Railway Operation Simulator  v2.20.3
A railway simulator for Windows
TrainUnit.cpp
Go to the documentation of this file.
1 // TrainUnit.cpp
2 /*
3  BEWARE OF COMMENTS in .cpp files: they were accurate when written but have
4  sometimes been overtaken by changes and not updated
5  Comments in .h files are believed to be accurate and up to date
6 
7  This is a source code file for "railway.exe", a railway operation
8  simulator, written originally in Borland C++ Builder 4 Professional with
9  later updates in Embarcadero C++Builder.
10  Copyright (C) 2010 Albert Ball [original development]
11 
12  This program is free software: you can redistribute it and/or modify
13  it under the terms of the GNU General Public License as published by
14  the Free Software Foundation, either version 3 of the License, or
15  (at your option) any later version.
16 
17  This program is distributed in the hope that it will be useful,
18  but WITHOUT ANY WARRANTY; without even the implied warranty of
19  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20  GNU General Public License for more details.
21 
22  You should have received a copy of the GNU General Public License
23  along with this program. If not, see <http://www.gnu.org/licenses/>.
24 */
25 // ---------------------------------------------------------------------------
26 #include <Classes.hpp>
27 #include <Controls.hpp>
28 #include <StdCtrls.hpp>
29 #include <Forms.hpp>
30 #include <Buttons.hpp>
31 #include <ExtCtrls.hpp>
32 #include <Menus.hpp>
33 #include <Dialogs.hpp>
34 #include <Graphics.hpp>
35 #include <ComCtrls.hpp>
36 #include <fstream>
37 #include <vector>
38 #include <algorithm> //for sort
39 #include <vcl.h>
40 #include <stdlib.h> //for rand() & random()
41 #include <math.hpp> //for speed & performance calcs
42 
43 #pragma hdrstop
44 
45 #include "TrainUnit.h"
46 #include "TrackUnit.h"
47 #include "GraphicUnit.h"
48 //#include "DisplayUnit.h" included in TrackUnit.h
49 #include "PerfLogUnit.h"
50 #include "Utilities.h"
51 
52 // ---------------------------------------------------------------------------
53 #pragma package(smart_init)
54 
56 
57 // ---------------------------------------------------------------------------
58 
59 int TTrain::NextTrainID = 0; // has to be initialised outside the class
60 
61 // ---------------------------------------------------------------------------
62 
63 TExitInfo::TExitInfo() //default constructor
64 {
65  ServiceReference = " ";
66  RepeatNumber = 0;
67  TimeToExitSecs = -1;
68 }
69 
70 // ---------------------------------------------------------------------------
71 
72 TTrain::TTrain(int Caller, int RearStartElementIn, int RearStartExitPosIn, AnsiString InputCode, int StartSpeedIn, int MassIn, double MaxRunningSpeedIn,
73  double MaxBrakeRateIn, double PowerAtRailIn, TTrainMode TrainModeIn, TTrainDataEntry *TrainDataEntryPtrIn, int RepeatNumberIn, int IncrementalMinutesIn,
74  int IncrementalDigitsIn, int SignallerMaxSpeedIn) : RearStartElement(RearStartElementIn), RearStartExitPos(RearStartExitPosIn), HeadCode(InputCode),
75  StartSpeed(StartSpeedIn), Mass(MassIn), MaxRunningSpeed(MaxRunningSpeedIn), MaxBrakeRate(MaxBrakeRateIn), PowerAtRail(PowerAtRailIn),
76  TrainMode(TrainModeIn), TrainDataEntryPtr(TrainDataEntryPtrIn), RepeatNumber(RepeatNumberIn), IncrementalMinutes(IncrementalMinutesIn),
77  IncrementalDigits(IncrementalDigitsIn), SignallerMaxSpeed(SignallerMaxSpeedIn)
78 /*
79  Construct a new train with general default values and input values for position and headcode.
80  Create the frontcode, headcode and background graphics here but don't delete them in a destructor.
81  This is because trains are kept in a vector and vectors erase elements during internal operations.
82  Deletion is explicit by using a special function. Increment the static class member NextTrainID
83  after setting this train's ID.
84 */
85 
86 {
87  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TTrain," + AnsiString(RearStartElementIn) + "," +
88  AnsiString(RearStartExitPosIn) + "," + AnsiString(InputCode) + "," + AnsiString(StartSpeedIn) + "," + AnsiString(MassIn) + "," +
89  AnsiString(TrainModeIn));
90  // AutoControl = true;//all trains start in auto control
91  UpdateCounter = 0;
92  TimeTimeLocArrived = false;
93  Derailed = false;
94  DerailPending = false;
95  Crashed = false;
96  StoppedAtBuffers = false;
97  StoppedAtSignal = false;
98  StoppedAtLocation = false;
99  StoppedAfterSPAD = false;
100  StoppedWithoutPower = false; // new at v2.4.0
101  StoppedForTrainInFront = false;
102  TrainInFront = false; //new at v2.18.0
103  SignallerStoppingFlag = false;
104  SignallerStopped = false;
105  SignallerRemoved = false;
106  NotInService = false;
107  HoldAtLocationInTTMode = false;
108  AllowedToPassRedSignal = false;
109  CallingOnFlag = false;
110  BeingCalledOn = false;
111  DepartureTimeSet = false;
113  TimetableFinished = false;
114  LastActionDelayFlag = false;
115  OneLengthAccelDecel = false;
116  TrainCrashedInto = -1;
118  Plotted = false;
119  TrainGone = false;
120  SPADFlag = false;
121  FrontCodePtr = new Graphics::TBitmap;
122  FrontCodePtr->PixelFormat = pf8bit;
123  FrontCodePtr->Height = 8;
124  FrontCodePtr->Width = 8;
126  FrontCodePtr->Transparent = false;
127  AValue = sqrt(2 * PowerAtRail / Mass);
129  TerminatedMessageSent = false;
130  JoinedOtherTrainFlag = false;
132  FollowOnServiceRef = ""; //added at v2.12.0
133  TreatPassAsTimeLocDeparture = false; //added at v2.12.0
134  StepForwardFlag = false;
136  for(int x = 0; x < 4; x++)
137  {
138  HeadCodeGrPtr[x] = new Graphics::TBitmap;
139  HeadCodeGrPtr[x]->PixelFormat = pf8bit;
140  HeadCodeGrPtr[x]->Height = 8;
141  HeadCodeGrPtr[x]->Width = 8;
143  HeadCodeGrPtr[x]->Transparent = false;
144  }
145  for(int x = 0; x < 4; x++)
146  {
147  BackgroundPtr[x] = new Graphics::TBitmap;
148  BackgroundPtr[x]->PixelFormat = pf8bit;
149  BackgroundPtr[x]->Height = 8;
150  BackgroundPtr[x]->Width = 8;
152  BackgroundPtr[x]->Transparent = false;
153  }
154  for(int x = 0; x < 4; x++)
155  {
157  // set here to ensure have values
158  }
159  for(int x = 0; x < 4; x++)
160  {
161  PlotElement[x] = -1; // marker for not plotted yet
162  }
163  for(int x = 0; x < 3; x++)
164  {
165  OldZoomOutElement[x] = -1; // marker for not plotted yet
166  }
168  NextTrainID++;
169 
170  // new values added to complete initialisation of all TTrain variables
171 
172  // ActionVectorEntryPtr = &(TrainDataEntryPtr->ActionVector.at(0)); can't be initialised yet as session trains created with Null
173  // TrainDataEntryPtr, initialise in AddTrain
175  FrontElementLength = 0;
176  EntrySpeed = 0;
177  ExitSpeedHalf = 0;
178  ExitSpeedFull = 0;
179  MaxExitSpeed = 0;
180  BrakeRate = 0;
181  CoastingBrakeRate = 0.03; //added at v2.18.0
183  FirstHalfMove = true;
184  EntryTime = 0;
185  ExitTimeHalf = 0;
186  ExitTimeFull = 0;
187  ReleaseTime = 0;
188  TRSTime = 0;
189  LastActionTime = 0;
190  Straddle = MidLag;
191  LeadElement = -1;
192  LeadEntryPos = 0;
193  LeadExitPos = 0;
194  MidElement = -1;
195  MidEntryPos = 0;
196  MidExitPos = 0;
197  LagElement = -1;
198  LagEntryPos = 0;
199  LagExitPos = 0;
200  TrainFailed = false; // added at v2.4.0
201  for(int x = 0; x < 4; x++)
202  {
203  HOffset[x] = 0;
204  VOffset[x] = 0;
205  PlotEntryPos[x] = 0;
206  }
207  OpTimeToAct = 60; // default value, new at v2.2.0
208  TimeToExit = -1;
209  ExitPair.first = -1;
210  ExitPair.second = -1;
211  MinsDelayed = 0.0; // new at v2.2.0
212  FirstLaterStopRecoverableTime = 0.0; // new at v2.2.0
213  FinishJoinLogSent = false;
214  // added at v2.4.0 to prevent repeatdly logging the event
217  // added at v2.4.0, no need to include in session file as will only be sent once & better that way
221  ZeroPowerNoCDTMessage = false;
226  ZeroPowerDepartMessage = false;
227  TrainInFrontMessage = false;
228  TrainFailurePending = false;
229  SkippedDeparture = false;
230  ActionsSkippedFlag = false;
231  SkipPtrValue = 0;
232  TrainSkippedEvents = 0;
233  DelayedRandMins = 0; //added at v2.13.0
234  NewDelay = 0; //added at v2.13.0
235  CumulativeDelayedRandMinsOneTrain = 0; //added at v2.13.0
236  ActualArrivalTime = TDateTime(0); //added at v2.13.0
237  LastSigPassedFailed = false; //added at v2.13.0
238  Utilities->CallLogPop(648);
239 }
240 
241 // ---------------------------------------------------------------------------
242 
243 void TTrain::DeleteTrain(int Caller)
244 /*
245  Delete train heap objects (bitmaps) explicitly by this special function rather than by a destructor, because vectors
246  erase elements during internal operations & if TTrain had an explicit destructor that deleted the heap elements then
247  it would be called when a vector element was erased. Calling the default TTrain destructor doesn't matter because all that
248  does is release the memory of the members (including pointers to the bitmaps), it doesn't destroy the bitmaps themselves.
249  It's important therefore to call this function before erasing the vector element, otherwise the pointers to the bitmaps
250  would be lost and the bitmaps never destroyed, thereby causing memory leaks.
251  No need to delete HeadCodePosition as that just points to existing bitmaps
252 */{
253  // if(NoDelete) return;//used when a TTrain is created to hold copied values from elsewhere
254  TrainController->LogEvent("" + AnsiString(Caller) + ",DeleteTrain," + HeadCode);
255  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",DeleteTrain," + HeadCode);
256  if(Display->ZoomOutFlag)
257  {
259  }
260  if(FrontCodePtr == 0)
261  {
262  throw Exception("Error in attempting to delete FrontCodePtr");
263  }
264  delete FrontCodePtr;
265  FrontCodePtr = 0;
266  for(int x = 0; x < 4; x++)
267  {
268  if(BackgroundPtr[x] == 0)
269  {
270  throw Exception("Error in attempting to delete BackgroundPtr[" + AnsiString(x) + "]");
271  }
272  delete BackgroundPtr[x];
273  BackgroundPtr[x] = 0;
274  }
275  for(int x = 0; x < 4; x++)
276  {
277  if(HeadCodeGrPtr[x] == 0)
278  {
279  throw Exception("Error in attempting to delete HeadCodeGrPtr[" + AnsiString(x) + "]");
280  }
281  delete HeadCodeGrPtr[x];
282  HeadCodeGrPtr[x] = 0;
283  }
284  Utilities->CallLogPop(649);
285 }
286 
287 // ---------------------------------------------------------------------------
288 
290 /*
291  Plots the train starting position on screen. Note that the check for starting on straight points &
292  on wrongly set points is carried out in TrainControllerUnit [but have to allow for starting on points because
293  ChangeDirection calls this function]. Train starts on Lead & Mid elements & Straddle = LeadMid unless
294  entering at a continuation in which case Straddle = MidLag & train not plotted immediately.
295  Set the headcode graphics pointers from the headcode text, then check whether starting at a
296  continuation. If so set Mid & Lag elements to -1 so they won't be plotted, and set Lead values
297  for the continuation element. Otherwise set Lead and Mid values,
298 
299  and Lead element value unless
300  Mid element is a buffer or continuation. Set Straddle, then for the Mid element set the graphic
301  offsets and headcode positions and front code. Pick up background bitmaps for the Mid element,
302  then check if a train on either Mid or Lag and if so give a warning message and return false so
303  that the calling function can delete the train. Plot the Mid element train values then do similarly
304  for the Lag element - set offsets, pick up background bitmaps, and plot the rear two segments of
305  the train. Finally set the Plotted flag and return true.
306 */{
307  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotStartPosition," + HeadCode);
308  int NextElementPosition, NextEntryPos, ElementLength, SpeedLimit;
309 
311  // PlotStartTime = TrainController->TTClockTime;
312  FirstHalfMove = true;
313 
314  // if enter at continuation then don't plot anything at start, but set TrainIDOnElement for continuation entry so as to
315  // 'claim' it for this train to prevent any other waiting trains trying to enter
317  {
318  LagElement = -1; // not to be plotted
319  LagExitPos = 0; // not to be plotted
320  LagEntryPos = 0; // not to be plotted
321  MidElement = -1; // not to be plotted
322  MidExitPos = 0; // not to be plotted
323  MidEntryPos = 0; // not to be plotted
325  LeadExitPos = 1; // will be 1 for continuation entry
326  LeadEntryPos = 0;
327 
329  MaxExitSpeed = StartSpeed; // initial value
331  ElementLength = Track->TrackElementAt(164, LeadElement).Length01;
332  SpeedLimit = Track->TrackElementAt(165, LeadElement).SpeedLimit01;
333  if(EntrySpeed > SpeedLimit)
334  {
335  EntrySpeed = SpeedLimit;
336  }
338  {
340  }
342  // LeadElement is the element to be entered
343 
344  // Precautionary check - If need to brake EntrySpeed may be too high, so set it to the speed at which
345  // can achieve ExitSpeedFull at the half braking rate.
347  {
348  double TempEntrySpeed = sqrt((MaxExitSpeed * MaxExitSpeed) + (3.6 * 3.6 * MaxBrakeRate * ElementLength)); // half braking
349  if(TempEntrySpeed < EntrySpeed)
350  {
351  EntrySpeed = TempEntrySpeed;
353  }
354  }
355  Straddle = MidLag; // only for starting on a continuation
357  // no need to stop gap flashing if start on continuation
358  }
359  else // not starting at a continuation
360  {
361  LagElement = -1;
362  LagEntryPos = 0;
363  LagExitPos = 0;
370 
372  MaxExitSpeed = StartSpeed; // initial value
374  bool TempDerail = false; // dummy
375  NextElementPosition = Track->TrackElementAt(168, LeadElement).Conn[Track->GetAnyElementOppositeLinkPos(2, LeadElement, LeadEntryPos, TempDerail)];
377  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0
378  {
379  StoppedWithoutPower = true;
380  }
381  // facing buffers check - ignore starting speed if start facing buffers
382  StoppedAtBuffers = false;
383  // need to set here as well as in UpdateTrain() in case paused during signaller change direction
386  {
387  FrontElementSpeedLimit = Track->TrackElementAt(494, LeadElement).SpeedLimit01; // use 01 for convenience, not used
388  FrontElementLength = Track->TrackElementAt(495, LeadElement).Length01; // use 01 for convenience, not used
389  EntrySpeed = 0;
390  ExitSpeedHalf = 0;
391  ExitSpeedFull = 0;
392  MaxExitSpeed = 0;
393  // SetTrainMovementValues not called so set this here
394  BrakeRate = 0;
397  StoppedAtSignal = false;
398  // new v2.2.0: can't be at buffers and signal! If was set then won't be reset as later
399  // signal check is an 'else'
400  if(!StoppedAtLocation)
401  {
402  StoppedAtBuffers = true; // stopped at location takes precedence
403  }
404  }
405 
406  // facing continuation check - don't allow to stop even if no power
408  {
409  FrontElementSpeedLimit = Track->TrackElementAt(509, LeadElement).SpeedLimit01; // use 01 for convenience, not used
410  FrontElementLength = Track->TrackElementAt(510, LeadElement).Length01; // use 01 for convenience, not used
414  BrakeRate = 0;
415  ExitTimeHalf = TrainController->TTClockTime + TDateTime(1.8 * (double) FrontElementLength / EntrySpeed / 86400);
416  ExitTimeFull = TrainController->TTClockTime + TDateTime(3.6 * (double) FrontElementLength / EntrySpeed / 86400);
417  }
418 
419  // Signal check
420  else if((NextElementPosition > -1) && (NextEntryPos > -1))
421  // condition check added as precaution after SloughIECC error reported by James U
422  {
423  if((Track->TrackElementAt(170, NextElementPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal) &&
424  (Track->TrackElementAt(171, NextElementPosition).Attribute == 0) && !StoppedWithoutPower)
425  {
426  FrontElementSpeedLimit = Track->TrackElementAt(172, LeadElement).SpeedLimit01; // use 01 for convenience, not used
427  FrontElementLength = Track->TrackElementAt(173, LeadElement).Length01; // use 01 for convenience, not used
428  EntrySpeed = 0;
429  ExitSpeedHalf = 0;
430  ExitSpeedFull = 0;
431  MaxExitSpeed = 0;
432  BrakeRate = 0;
435  if(!StoppedAtLocation) //if it is stopped at location then don't want StoppedAtSignal until departure time if still red then, & UpdateTrain takes care of thet
436  {
437  StoppedAtSignal = true;
439  // TrainController->LogActionError(39, HeadCode, "", SignalHold, Track->TrackElementAt(754, NextElementPosition).ElementID);
440  }
442  {
443  // set both StoppedAtLocation & StoppedAtSignal, so that 'pass stop signal' is offered in popup menu rather than move
444  // forwards, but don't change the background colour so still shows as stopped at location
445  StoppedAtSignal = true;
446  }
447  }
448  else
449  {
450  StoppedAtSignal = false;
451  if(NextEntryPos > 1)
452  {
453  ElementLength = Track->TrackElementAt(174, NextElementPosition).Length23;
454  SpeedLimit = Track->TrackElementAt(175, NextElementPosition).SpeedLimit23;
455  }
456  else
457  {
458  ElementLength = Track->TrackElementAt(176, NextElementPosition).Length01;
459  SpeedLimit = Track->TrackElementAt(177, NextElementPosition).SpeedLimit01;
460  }
461  if(EntrySpeed > SpeedLimit)
462  {
463  EntrySpeed = SpeedLimit;
464  }
466  {
468  }
470  TDateTime TestTime = TrainController->TTClockTime; // test
471  AnsiString TimeString = Utilities->Format96HHMMSS(TestTime); // test
472  SetTrainMovementValues(2, NextElementPosition, NextEntryPos);
473  // NextElement is the element to be entered
474 
475  // Precautionary check - If need to brake EntrySpeed may be too high, so set it to the speed at which
476  // can achieve ExitSpeedFull at the half braking rate.
478  {
479  double TempEntrySpeed = sqrt((MaxExitSpeed * MaxExitSpeed) + (3.6 * 3.6 * MaxBrakeRate * ElementLength));
480  // half braking
481  if(TempEntrySpeed < EntrySpeed)
482  {
483  EntrySpeed = TempEntrySpeed;
484  SetTrainMovementValues(3, NextElementPosition, NextEntryPos);
485  }
486  }
487  }
488  }
490  {
491  throw Exception("Error, LeadElement Exit Connection is NotSet");
492  }
493  }
494  if(MidElement > -1) // will be -1 if start on continuation
495  {
496  Straddle = LeadMid;
500  {
501  for(int x = 0; x < 4; x++)
502  {
503  HeadCodePosition[x] = HeadCodeGrPtr[3 - x];
504  }
505  }
506  else
507  {
508  for(int x = 0; x < 4; x++)
509  {
511  }
512  }
513  if(TrainMode == Timetable)
514  {
516  }
517  else
518  {
520  }
522  // pick up background bitmaps [0] & [1] & plot HeadCodes [0] & [1]
523 
526 /* Move check to AddTrain, also, now that can start on bridges need to check that other train is on same track before refusing
527  if((Track->TrackElementAt(182, LeadElement).TrainIDOnElement > -1) || ((MidElement > -1) && (Track->TrackElementAt(183, MidElement).TrainIDOnElement > -1)))
528  {
529  ShowMessage("Can't place train " + HeadCode + "; another train already present at location");
530  Utilities->CallLogPop(651);
531  return false;
532  }
533 */
538  PlotTrainGraphic(8, 0, Display);
539  PlotTrainGraphic(9, 1, Display);
540 
543 
544  // pick up background bitmaps [2] & [3]
545 
548 
549  PlotElement[2] = MidElement;
551  PlotElement[3] = MidElement;
553  PlotTrainGraphic(10, 2, Display);
554  PlotTrainGraphic(11, 3, Display);
555  // Plotted = true; set in PlotTrainGraphic
556  }
557  Display->Update(); // resurrected when Update() dropped from PlotOutput etc
558  Utilities->CallLogPop(652);
559 }
560 
561 // ---------------------------------------------------------------------------
562 void TTrain::UnplotTrain(int Caller)
563 {
564  // Note: If trouble is experienced with the PlotAlternativeTrackRouteGraphic functions remove them & test for train on a bridge and if so call Clearand..
565  if(!Plotted)
566  {
567  return;
568  }
569  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",UnplotTrain," + HeadCode);
570 
571  if(Straddle == MidLag)
572  {
573  if(MidElement > -1)
574  {
579  // to force plot of locked route marker, needed once only for the element
580  }
581  if(LagElement > -1)
582  {
587  // to force plot of locked route marker, needed once only for the element
588  }
589  }
590  else if(Straddle == LeadMidLag)
591  {
592  if(LeadElement > -1)
593  {
596  // to force plot of locked route marker, needed once only for the element
597  }
598  if(MidElement > -1)
599  {
604  // to force plot of locked route marker, needed once only for the element
605  }
606  if(LagElement > -1)
607  {
610  // to force plot of locked route marker, needed once only for the element
611  }
612  }
613  else if(Straddle == LeadMid)
614  {
615  if(LeadElement > -1)
616  {
621  // to force plot of locked route marker, needed once only for the element
622  }
623  if(MidElement > -1)
624  {
629  // to force plot of locked route marker, needed once only for the element
630  }
631  }
632  if(LeadElement > -1)
633  {
635  }
636  if(MidElement > -1)
637  {
639  }
640  if(LagElement > -1)
641  {
643  }
644  Plotted = false;
646  Display->Update();
647  // without this the screen 'blinks' at next Clearand... prob forces a full repaint for some reason
648  // resurrected when Update() dropped from PlotOutput etc
649  Utilities->CallLogPop(653);
650 }
651 
652 // ----------------------------------------------------------------------------
653 
654 void TTrain::UpdateTrain(int Caller)
655 /*
656  Note: Some changes made since comments written
657 
658  Brief:
659  Enter with Straddle defining train position wrt Lag, Mid & Lead elements. Is only MidLag at this point
660  on first entry at a continuation (with no train plotted), in all other cases it is either LeadMid (when train fully
661  on Lead & Mid elements) or LeadMidLag (when train straddling 3 elements).
662  Thereafter on entry Straddle = LeadMidLag or LeadMid; LeadMid if train fully on Mid & Lead elements, and
663  LeadMidLag if on Lag, Mid and Lead elements (back on lag, front on Lead, & middle 2 segments on Mid).
664  If enter with Straddle = LeadMid, then train is in effect in the first half of the next element, and moves half onto it after
665  the half time point has been passed. The values for the next element were set when the train was last updated when Straddle became
666  LeadMid from LeadMidLag. After the half time point has been passed Straddle is
667  changed to MidLag within the function and all elements moved down one, old Mid becomes
668  the new lag, old Lead becomes the new Mid, and a new Lead is obtained. Then the new positions are plotted, and finally Straddle is
669  incremented to reflect the position the train now occupies.
670 
671  Detail:
672  Set TrainFailurePending if all conditions met
673  Check whether stopped at a non-red signal, and if so reset StoppedAtSignal so train can move.
674  Check whether buffers at immediate exit, either when first enter the function or later, and set StoppedAtBuffers if so
675  and return.
676  If Straddle == LeadMid then train fully on Lead and Mid, so ready for a major update:-
677  If there's a LagElement (there will be but include check for good practice - next
678  function depends on it) Check whether DerailPending set - set during last GetLeadElement if appropriate but only acted on here when
679  train fully on offending point - Derail set and DerailPanding reset, train background
680  colour changed (note that BackgroundColour is a property of the train itself) then return.
681  If no derail pending reset Lag and Mid elements to the old Mid and Lead values, reset Straddle to MidLag, then set
682  the new LeadElement, which will be the next connected element (obtain using GetLeadElement) or -1 if the current
683  LeadElement is an exit continuation. During GetLeadElement the element at LeadElement is checked and if a stop
684  signal is found StoppedAtSignal is set to true, otherwise StoppedAtSignal is set to false. Also Derail is set
685  if LeadElement is a fouled trailing point.
686  Now, the train is moved on by one segment. Firstly the last BackgroundElement is set to LagElement, then the last
687  segment of the LagElement is unplotted (if there is a LagElement - may be entering at a continuation), by
688  replotting the last background segment and checking whether the element is a bridge or crossover with the other
689  track in a route, in which case the route colour is replotted.
690  Then, if Straddle == LeadMidLag (train will move completely off the element during this function), and the train
691  track is in a route, then all the train elements are removed from the route unless it's an autosig route. Normally only the
692  LeadElement will be in a route for a moving train, but when originally placed all elements may be in the route so check them all.
693  Note also that there may be two routes at a given element position, but only one of them is the correct one, so this
694  is identified prior to the removal. Also the TrainIDs are reset because the train will be fully off this element at the end of
695  the function. If Straddle == LeadMidlag and the element being left is a ContinuationExit the the TrainGone flag is set so the
696  train can be deleted by the calling function, and the function returns.
697  If the element is a signal in the train movement direction, then it is reset to red (Attribute = 0) and is replotted
698  to show the red aspect. Finally if element is a signal in the other direction it is replotted as it was - need to
699  plot individually because could have any aspect, the background bitmap that was picked up earlier contains just the
700  basic red aspect.
701 
702  Now all the array values are updated, but the [0] values are as yet invalid, these have to be obtained explicitly from
703  the new LeadElement later. The headcode graphics are updated so that it reads correctly - left to right & top to bottom,
704  regardless of direction, and with the correct front code colour.
705 
706  The new front segment background bitmap is now picked up and the graphic offsets set, and the segments are plotted.
707  No more unplotting is needed as all but the last segment are overwritten by later segments, and the new front
708  segment is just plotted, though the background bitmap at that location has to be picked up. Just where they are
709  plotted depends on the Straddle value, [0] is always on Lead, [1] is on Lead if Straddle == LeadMidLag or Mid if
710  Straddle == MidLag; [2] is always on Mid, and [3] is on Mid if Straddle == LeadMidLag or Lag if Straddle == MidLag.
711  Also prior to plotting the lead segment a crash check is made, and if true the Crashed flag is set and the
712  TrainCrashedInto value also set to the current TrainID - this is so it too becomes crashed and hence stopped.
713 
714  The Crashed flag is now checked, and if set the front headcode colour is changed to the same as the rest of the code,
715  and the background colour changed. Then the train that is crashed into is also set to Crashed, and its colours
716  changed similarly. The function then returns.
717 
718  If Crashed is not set then Straddle is incremented and the function returns.
719 */
720 
721 {
722  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",UpdateTrain," + HeadCode);
723  UpdateCounter++;
724  // 100 counts = 5secs (used in splits to prevent too frequent length checks in front & rear splits)
725  if(UpdateCounter >= 100)
726  {
727  UpdateCounter = 0;
728  }
729  int RandRange = (TrainController->MTBFHours * 3600) / 53;
730 
731  // MTBFHours is in timetable clock hours, min value is 1 & max value is 9,999 (integer values on input)
732  // but double on use because it represents timetable clock time, so at 1/16 speed RandRange is * 16 (160,000 max) & at 16x speed its /16 (1/16 min)
733  // i.e MTBFHours is Input value/TTClockSpeed (conversion is done in InterfaceUnit)
734  if(int(TrainController->RandomFailureCounter) == (rand() % 1060))
735  // RandomFailureCounter value is fixed for a full cycle of train updates so this
736  // makes sure there's no bunching of failures as there is for a fixed comparison number
737  // or a small range of comparison numbers. True every 53 secs (real time) on average rand()
738  // gives a random number between 0 and 16384 (defined as RAND_MAX in stdlib.h)
739  {
740  if(!TrainFailed && !TrainOnContinuation(0) && (RandRange > 0) && (PowerAtRail > 1) && !((TrainMode == Timetable) && TimetableFinished)
741  && !Crashed && !Derailed && !((TrainMode == Signaller) && Stopped()))
742  // RandomFailureCounter resets to 0 every 53 secs, if RandRange is 0 then no failure rate is set - i.e. failure rate = 0
743  // don't fail if:
744  // (a) on a continuation (entering or leaving);
745  // (b) already failed;
746  // (c) power effectively zero (8000) min value for powered; 0.08 for 'no power';
747  // (d) train terminated;
748  // (e) crashed or derailed; or
749  // (f) under signaller control and stopped.
750  // (g) TreatPassAsTimeLocDeparture is true //added at v2.12.0
751  {
752  if((random(RandRange) == 0) && !TreatPassAsTimeLocDeparture)
753  // max value for RandRange is over 2x10^9
754  {
755  // here if failure due
756  TrainFailurePending = true;
757  // the failure occurs when PlotElements set to proper Lead & Mid Elements
758  }
759  }
760  }
761 /* dropped as it allows a train to stop on a half element - when reach (if Stopped()) at line 1310
762  if ((PowerAtRail < 1) && (EntrySpeed < 1)) // added at v2.4.0
763  {
764  StoppedWithoutPower = true;
765  }
766 */
767 // float TimeToExit; //added at v2.10.0 Removed these so original values retained - used when train on continuation
768 // THVShortPair ExitPair; //added at v2.10.0
769  int LockedVectorNumber;
770  Graphics::TBitmap *EXGraphicPtr = RailGraphics->bmTransparentBgnd;
771  // default values - these needed for route checker below
772  Graphics::TBitmap *EntryDirectionGraphicPtr = RailGraphics->bmTransparentBgnd;
773 
775  {
777  }
778  if(Crashed || Derailed)
779  {
781  {
782  PlotTrain(7, Display);
783  // replotted every cycle because of level crossing crashes, otherwise a flashing level crossing wipes out half of the train
784  Display->Update();
785  }
786  OpTimeToAct = 0.0;
787  // need to set this here as wouldn't be calculated otherwise as return from UpdateTrain
788  Utilities->CallLogPop(1017);
789  return; // no further action, user has to remove or work around
790  }
791 
793  {
795  }
797  {
799  }
800  if(StoppedWithoutPower && (TrainMode == Signaller)) //added at v2.13.2 as this condition not covered - was shown as normal
801  {
803  }
805  // introduced at v1.2.0, formerly 'TimeTimeLocArrived = false' was included
806  // in the next condition 'if(!Stopped() && !SPADFlag)' which led to repeated arrival messages if signaller control allowed a train
807  // to move & then stop again at the same station
808  {
809  TimeTimeLocArrived = false;
810  }
811  if(!Stopped() && !SPADFlag && !TrainFailed)
812  {
814  }
815  // set or release StoppedAtBuffers if fully on 2 elements depending on LeadElement
816  // Note that if LeadElement == Buffers train must be facing the buffer so no need to check orientation
817 /* old version where force a stop at buffers regardless of speed
818  if((Straddle == LeadMid) && (LeadElement > -1) && (Track->TrackElementAt(, LeadElement).TrackType == Buffers)) StoppedAtBuffers = true;
819  else StoppedAtBuffers = false;
820 */
821 
822  // new version where crash if run into buffers
823  if(!Crashed)
824  {
825  if((Straddle == LeadMid) && (LeadElement > -1) && (Track->TrackElementAt(602, LeadElement).TrackType == Buffers))
826  {
827  if(ExitSpeedFull > 1)
828  {
829  Crashed = true;
833  // SendMissedActionLogs(3, -1, ActionVectorEntryPtr);//-1 is a marker for send messages for all remaining entries, including Fer if present
834  // no need for missed action logs - will be sent when train removed
835  StoppedAtBuffers = false;
836  }
838  // stopped at location & stopped without power take precedence
839  {
840  StoppedAtBuffers = true;
841  }
842  else
843  {
844  StoppedAtBuffers = false;
845  }
846  }
847  else
848  {
849  StoppedAtBuffers = false;
850  }
851  }
852  else
853  {
854  StoppedAtBuffers = false;
855  }
856  // if crashed don't want stopped at buffers set
857 
858  // also crash if run into a level crossing that is changing or has barriers up
859  if(!Crashed)
860  {
861  if((Straddle == LeadMid) && (LeadElement > -1) && (ExitSpeedFull > 1))
862  {
863  int H = Track->TrackElementAt(873, LeadElement).HLoc;
864  int V = Track->TrackElementAt(874, LeadElement).VLoc;
865  if(Track->IsLCAtHV(40, H, V) && !Track->IsLCBarrierDownAtHV(2, H, V))
866  {
867  Crashed = true;
871  // no need for missed action logs - will be sent when train removed
872  }
873  }
874  }
876  {
878  }
879  // set or reset HoldAtLocationInTTMode (if true then actions are needed before train departs)
881  //if Command == "" then either TimeLoc or TimeTimeLoc so don't hold, and added last part at v2.12.0 so don't hold if have both command == pas and Treat... flag
882  {
883  HoldAtLocationInTTMode = true;
884  }
885  else if(TrainMode == Timetable)
886  {
887  HoldAtLocationInTTMode = false;
888  }
889  // in Signaller mode HoldAtLocationInTTMode not changed
890 
891  // check if departure pending & set times unless already set
892  if(TrainMode == Timetable)
893  {
895  // && !StoppedAtBuffers) - drop this, set times whether or not at buffers
896  {
897  if((ActionVectorEntryPtr->Command != "pas") && (ActionVectorEntryPtr->DepartureTime > TDateTime(-1)) && (ActualArrivalTime > TDateTime(0)))
898  {
899  AnsiString ReasonArray[24] = {"a driver is awaited","a guard is awaited","of a medical emergency","of a technical problem","of a security issue",
900  "of a safety issue","of a disturbance","a train crew member has been taken ill","the driver has been taken ill","the guard has been taken ill",
901  "a report has been received concerning safety","a shoe has been lost under the train","of a reported theft",
902  "of an incident involving an animal","some luggage has been lost under the train","a minor repair is needed","a suspicious object has to be dealt with safely",
903  "a door is stuck open","additional stock has to be attached","a security alert","of a train fault","of an operating incident","safety checks are required",
904  "of a shortage of on train crew"};
905  //(ActionVectorEntryPtr->Command != "pas") added at v2.13.0 to rule out passes, though probably not needed
906  //(ActualArrivalTime > TDateTime(0)) added at v2.13.0 to ensure that it has been set and to dismiss trains that are present
907  //at start or have no departure time set.
908  NewDelay = 0; //section relating to random delays added at v2.13.0
909  TDateTime TimetableReleaseTime = TrainController->GetRepeatTime(0, ActionVectorEntryPtr->DepartureTime, RepeatNumber, IncrementalMinutes); //Timetable value
910  TDateTime DwellTime = TimetableReleaseTime - ActualArrivalTime; //Timetable value
911  if(DwellTime < TDateTime(30.0 / 86400))
912  {
913  DwellTime = TDateTime(30.0 / 86400);
914  }
915  int randval = random(10000);
916  if(randval != 0) //if randval == 0 or DelayMode == Nil then NewDelay will be 0 as set above
917  {
918  if(Utilities->DelayMode == Minor)
919  {
920  if(randval < Utilities->MinorDelayCutoff)
921  {
922  NewDelay = Utilities->MinorDelayFactor * log(Utilities->MinorDelayCutoff/randval);//minutes (confusingly log in C++Builder gives the natural logarithm)
923  }
924  }
925  else if(Utilities->DelayMode == Moderate)
926  {
927  if(randval < Utilities->ModerateDelayCutoff)
928  {
930  }
931  }
932  else if(Utilities->DelayMode == Major)
933  {
934  if(randval < Utilities->MajorDelayCutoff)
935  {
937  }
938  }
939  }
940 //NewDelay = 25; //test
941  if(double(TrainController->TTClockTime) <= (Utilities->LastDelayTTClockTime + 5.0/1440.0)) //if within 5 mins of last delay for any train
942  { //then don't delay. Added at v2.13.0
943  NewDelay = 0;
944  }
945  if(NewDelay < 1)
946  {
947  NewDelay = 0;
948  }
949  if(NewDelay < double(DwellTime) * 1440) //if less than scheduled dwell time then no additional delay
950  {
951  NewDelay = 0;
952  }
953  else
954  {
955  NewDelay -= double(DwellTime) * 1440;//reduce delay by dwell time
956  }
957  if(DelayedRandMins > 0)
958  {
959  DelayedRandMins -= double(DwellTime) * 1440;//reduce knock-on random delay by dwell time
960  }
961  if(DelayedRandMins < 0)
962  {
963  DelayedRandMins = 0;//can't be less than zero
964  }
966  {
967  NewDelay -= DelayedRandMins; //NewDelay is the additional delay over and above the existing knock-on delay (from earlier random delays)
968  //the formula above already includes knock-on effects
969  DelayedRandMins += NewDelay; //the new total delay, knock-on + additional
970 // CumulativeDelayedRandMinsOneTrain += DelayedRandMins; //don't add here, add when depart, else this value can be > late mins
971  }
972  else
973  {
974  NewDelay = 0;
975 // CumulativeDelayedRandMinsOneTrain += DelayedRandMins; //as above
976  }
977  ReleaseTime = LastActionTime + TDateTime(NewDelay / 1440); //earliest possible release time
978  if(NewDelay < 0.5) //less than the 30 secs min interval
979  {
980  ReleaseTime = LastActionTime + TDateTime(30.0 / 86400);
981  }
982  if(ReleaseTime < TimetableReleaseTime)
983  {
984  ReleaseTime = TimetableReleaseTime; //back to correct time
985  NewDelay = 0;
986  DelayedRandMins = 0;
987  }
988  if(DelayedRandMins > double(ReleaseTime - TimetableReleaseTime) * 1440)
989  {
990  DelayedRandMins = double(ReleaseTime - TimetableReleaseTime) * 1440; //reduce this if time has been made up
991  }
992 
993  if(DelayedRandMins < NewDelay) //may be if reduced above, but if so need to reduce NewDelay also
994  {
996  }
997  //may be possible to simplify all the above but as it seems to work ok leave as is
998  if(int(NewDelay) > 0) //additional delay over and above knock-on effects from earlier random delays
999  {
1001  if(int(NewDelay) == 1)
1002  {
1003  Display->WarningLog(12, Utilities->Format96HHMMSS(TrainController->TTClockTime) + ": " + HeadCode + " delayed at " +
1004  ActionVectorEntryPtr->LocationName + " by 1 minute");
1005  PerfLogForm->PerformanceLog(18, Utilities->Format96HHMMSS(TrainController->TTClockTime) + " WARNING: " + HeadCode + " delayed at " +
1006  ActionVectorEntryPtr->LocationName + " by 1 minute because of a minor technical issue");
1007  TrainController->StopTTClockMessage(140, HeadCode + " delayed at " +
1008  ActionVectorEntryPtr->LocationName + " by 1 minute because of a minor technical issue");
1009  }
1010  else
1011  {
1012  Display->WarningLog(11, Utilities->Format96HHMMSS(TrainController->TTClockTime) + ": " + HeadCode + " delayed at " +
1013  ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) + " minutes");
1014  if(NewDelay >= 10) //give variable reasons for >= 10 mins
1015  {
1016  int randval2 = rand() % 24; //24 reasons
1017  AnsiString Reason = ReasonArray[randval2];
1019  HeadCode + " delayed at " + ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) +
1020  " minutes because " + Reason);
1021  TrainController->StopTTClockMessage(141, HeadCode + " delayed at " + ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) +
1022  " minutes because " + Reason);
1023  }
1024  else
1025  {
1027  HeadCode + " delayed at " + ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) +
1028  " minutes because of a minor problem");
1029  TrainController->StopTTClockMessage(142, HeadCode + " delayed at " + ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) +
1030  " minutes because of a minor problem");
1031  }
1032  }
1033  }
1034  TRSTime = ReleaseTime - TDateTime(10.0 / 86400);
1035  ActualArrivalTime = TDateTime(0); //only run through this section once per arrival
1036  DepartureTimeSet = true;
1037  }
1038  else if(ActionVectorEntryPtr->DepartureTime > TDateTime(-1)) //as was, for trains that don't have an errival time set
1039  {//if have skipped to a new service then DepartureTime will be set (in above segement when earlier train arrived)
1040  //but ArrivalTime won't be set as it is reset to 0 at end of above segement when earlier train arrived, so this segement
1041  //will run without any random delays which might cause additional complications from mixing modifications and best avoided.
1042  NewDelay = 0;
1043  DelayedRandMins = 0;
1045  if(ReleaseTime <= LastActionTime + TDateTime(30.0 / 86400))
1046  {
1047  ReleaseTime = LastActionTime + TDateTime(30.0 / 86400);
1048  }
1049  TRSTime = ReleaseTime - TDateTime(10.0 / 86400);
1050  DepartureTimeSet = true;
1051  }
1052  else if((ActionVectorEntryPtr->Command == "pas") && TreatPassAsTimeLocDeparture) //new segment at v2.12.0 to treat a pass as a departure
1053  {//for when skip to a new service at a pass location. As above this also avoids any random delays, and will avoid above segment because
1054  //departure time isn't set - it's an event time. Again random delays in this situation might cause additional complications
1055  //from mixing modifications so best avoided.
1056  NewDelay = 0;
1057  DelayedRandMins = 0;
1059  if(ReleaseTime <= LastActionTime + TDateTime(30.0 / 86400))
1060  {
1061  ReleaseTime = LastActionTime + TDateTime(30.0 / 86400);
1062  }
1063  TRSTime = ReleaseTime - TDateTime(10.0 / 86400);
1064  DepartureTimeSet = true;
1065  }
1066  }
1067  }
1068  if(TrainController->OpTimeToActUpdateCounter == 0)// && TrainController->OpActionPanelVisible) removed last condition so always calc TimeToExit
1069  {
1070  OpTimeToAct = CalcTimeToAct(0, TimeToExit, ExitPair); // called after ReleaseTime set
1071 // this->TimeToExit = TimeToExit; don't need these as values updated directly
1072 // this->ExitPair = ExitPair;
1073  // calculate every 1 sec (in real time, not timetable time) for all trains
1074  }
1075  // check if being held at location pending any actions & deal with them if time appropriate & >= 30s since LastActionTime
1076  if(TrainMode == Timetable)
1077  {
1078  if((ActionVectorEntryPtr->Command != "Frh") && (ActionVectorEntryPtr->Command != "Frh-sh"))
1079  {
1080  RemainHereLogNotSent = true;
1081  }
1083  {
1084  // ignore TimeLoc & TTLoc departures
1085  // Action logs given in functions
1087  LastActionTime + TDateTime(30.0 / 86400)))
1088  {
1089  if(ActionVectorEntryPtr->Command == "fsp")
1090  {
1091  // added for v1.3.2 because when add new train to TrainVector 'this' address likely invalidated, hence make no more changes to
1092  //'this' train. Next clock cycle will deal with any required changes
1093  FrontTrainSplit(0);
1094  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1095  { //this is checked before each return
1096  TrainHasFailed(0);
1097  }
1098  Utilities->CallLogPop(2041);
1099  return;
1100  }
1101  else if(ActionVectorEntryPtr->Command == "rsp")
1102  {
1103  // added for v1.3.2 because when add new train to TrainVector 'this' address likely invalidated, hence make no more changes to
1104  //'this' train. Next clock cycle will deal with any required changes
1105  RearTrainSplit(0);
1106  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1107  {
1108  TrainHasFailed(1);
1109  }
1110  Utilities->CallLogPop(2042);
1111  return;
1112  }
1113  else if(ActionVectorEntryPtr->Command == "Fjo")
1114  {
1115  FinishJoin(0);
1116  }
1117  else if(ActionVectorEntryPtr->Command == "jbo")
1118  {
1119  JoinedBy(0);
1120  }
1121  else if(ActionVectorEntryPtr->Command == "cdt")
1122  {
1123  ChangeTrainDirection(0, false);
1124  }
1125  else if(ActionVectorEntryPtr->Command == "dsc")
1126  {
1127  Description = ActionVectorEntryPtr->NewDescription; //changed at v2.16.1
1131  }
1132  else if(ActionVectorEntryPtr->Command == "Fns")
1133  {
1134  NewTrainService(0, false);
1135  }
1136  else if(ActionVectorEntryPtr->Command == "Frh")
1137  {
1138  RemainHere(0);
1139  }
1140  else if(ActionVectorEntryPtr->Command == "Fer")
1141  {
1142  TimetableFinished = true;
1143  }
1144  // other aspects of 'Fer' dealt with in TTrain::HasTrainGone()
1145  else if(ActionVectorEntryPtr->Command == "F-nshs")
1146  {
1148  }
1149  else if(ActionVectorEntryPtr->Command == "Frh-sh")
1150  {
1151  RepeatShuttleOrRemainHere(0, false);
1152  }
1153  else if(ActionVectorEntryPtr->Command == "Fns-sh")
1154  {
1156  }
1157 /*
1158  F-nshs (FNSShuttle) = Finish New Service (Shuttle) = finish, form new shuttle service in same direction, details =
1159  shuttle headcode (no train creation)
1160  Frh-sh (TimeCmdHeadCode) = Finish then restart as a shuttle using Snt-sh or Sns-sh, when all shuttle repeats done
1161  remain here
1162  Fns-sh (FSHNewService) = Finish then restart as a shuttle using Snt-sh or Sns-sh, when all shuttle repeats done
1163  form new service via Sns-fsh using the NonRepeatingShuttleLinkHeadCode
1164 */
1165  }
1166  }
1167  else
1168  {
1170  {
1172  }
1173  }
1174  }
1175  if(TrainMode == Timetable)
1176  {
1177  if(StoppedAtBuffers)
1178  {
1179  // error if buffers (& element before it) not at a location, or if buffer location different to ActionVectorEntryPtr location
1180  // if buffer location same as ActionVectorEntryPtr location & not Frh then error will be given for inability to depart
1181  AnsiString BufferLocation = Track->TrackElementAt(604, LeadElement).ActiveTrackElementName;
1182  if(BufferLocation == "")
1183  {
1185  }
1186  AnsiString ExpectedLocation = ActionVectorEntryPtr->LocationName;
1187  if((BufferLocation == "") || (BufferLocation != ExpectedLocation))
1188  {
1192  {
1194  // SendMissedActionLogs(-1, ActionVectorEntryPtr);//-1 is a marker for send messages for all remaining entries, including Fer if present
1195  // Drop missed actions so user can still use sig mode to get back on track
1197  }
1198  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1199  {
1201  TrainHasFailed(2);
1202  }
1203  Utilities->CallLogPop(1020);
1204  return;
1205  }
1206  else if((BufferLocation != "") && (BufferLocation == ExpectedLocation) && DepartureTimeSet && !RevisedStoppedAtLoc() && (TrainController->TTClockTime >
1207  ReleaseTime))
1208  {
1211  {
1214  // SendMissedActionLogs(-1, ActionVectorEntryPtr);//-1 is a marker for send messages for all remaining entries, including Fer if present
1215  // Drop missed actions so user can still use sig mode to get back on track
1217  }
1218  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1219  {
1221  TrainHasFailed(3);
1222  }
1223  Utilities->CallLogPop(1397);
1224  return;
1225  }
1226  }
1227  else
1228  {
1230  }
1231  }
1232  else
1233  {
1235  }
1236  if(TrainMode == Timetable)
1237  {
1239  {
1241  }
1243  {
1245  }
1246  }
1247  // Pick up element next to the train front (if exists) to check for calling-on, restart after a cleared signal, or
1248  // restart after stopped for train in front
1249  int NextElementPosition, NextEntryPos;
1250 
1251  if(LeadElement > -1) // if an exit continuation then not set
1252  {
1253  if((Track->TrackElementAt(186, LeadElement).TrackType != Points) || ((LeadEntryPos != 0) && (LeadEntryPos != 2)))
1254  {
1256  }
1257  else if((Track->TrackElementAt(187, LeadElement).TrackType == Points) && ((LeadEntryPos == 0) || (LeadEntryPos == 2)))
1258  {
1259  if(Track->TrackElementAt(188, LeadElement).Attribute == 0)
1260  {
1261  LeadExitPos = 1;
1262  }
1263  else
1264  {
1265  LeadExitPos = 3;
1266  }
1267  }
1268  NextElementPosition = Track->TrackElementAt(189, LeadElement).Conn[LeadExitPos];
1269  NextEntryPos = Track->TrackElementAt(190, LeadElement).ConnLinkPos[LeadExitPos];
1270  }
1271  else
1272  {
1273  NextElementPosition = -1;
1274  NextEntryPos = -1;
1275  }
1276  if((NextElementPosition > -1) && (NextEntryPos > -1))
1277  // may be buffers or continuation so need this check
1278  {
1279 /*
1280  Check whether calling-on conditions met:-
1281  a) approaching train has stopped at a signal but not at a location;
1282  b) if there is a facing train at the station, it is being held appropriately (must be awaiting a join (Fjo or jbo) or a
1283  change of direction (cdt), remaining here (Frh), or under signaller control);
1284  c) at least 1 platform available for the approaching train;
1285  d) points (if any) set for direct route into platform;
1286  e) approaching train is to stop at station;
1287  f) no more facing signals between train and platform;
1288  g) [dropped g]
1289  h) train in front preventing route being set far enough to release stop signal;
1290  i) train in front not exiting at continuation;
1291  j) signal must be within 4km of the stop platform;
1292  k) [dropped (k), now can set a reoute or part route into platform so can set points more easily];
1293  l) no existing route conflicts with the route into the platform; and
1294  m) not failed or without power (these added at v2.10.0)
1295  If all OK & route or part route not already set then set an unrestricted route into the station (just to the first platform), to prevent point changing or
1296  other route conflicts - if a partial route set than can still change points outside the route or have a route conflict if another route is set.
1297 */
1298  if(TrainMode == Timetable)
1299  {
1300  if(CallingOnAllowed(0)) //returns false if failed or no power (modified afer v2.9.2)
1301  {
1302  CallingOnFlag = true;
1303  PlotTrainWithNewBackgroundColour(1, clCallOnBackground, Display); // calling-on background
1304  }
1305  else
1306  {
1307  if(CallingOnFlag) //TrainHasFailed sets this flag to false (at v2.10.0)
1308  {
1309  if(!TrainFailed) //shouldn't be needed but include for safety at v2.10.0
1310  {
1312  }
1313  }
1314  CallingOnFlag = false;
1315  }
1316  }
1317  if(StoppedAtSignal && ((Track->TrackElementAt(191, NextElementPosition).Attribute > 0) || AllowedToPassRedSignal) && !TrainFailed && !RevisedStoppedAtLoc())
1318  {
1319  //'&& !StoppedAtLocation' added at v2.7.0 as if had been stopped at signal before tt control restored then background colour changed to normal when signal changed from red
1320  // reset PassRedSignal when reached half-way point in next element, if reset here then SetTrainMovementValues
1321  // sets StoppedAtSignal again & train doesn't move
1322  StoppedAtSignal = false;
1323  // need to recalculate exit times since old entry time expired. Straddle now at MidLag with front of train on MidElement
1324  // hence use MidElement for the calculation so same as would have been used if signal not red, when Straddle was
1325  // LeadMidLag and front of train was on LeadElement (after the current move)
1327  EntrySpeed = 0;
1329  FirstHalfMove = true;
1330  SetTrainMovementValues(4, NextElementPosition, NextEntryPos);
1331  // NextElement is the element to be entered
1332  }
1333  if((LeadElement > -1) && (LeadExitPos > -1))//this section added at v2.18.0
1334  {
1335  int NextPos = Track->TrackElementAt(649, LeadElement).Conn[LeadExitPos];
1336  if(NextPos > -1)
1337  {
1338  int NextEntryPos = Track->TrackElementAt(1674, LeadElement).ConnLinkPos[LeadExitPos];
1339  if(Track->OtherTrainOnTrack(1, NextPos, NextEntryPos, TrainID))
1340  // true if another train on NextEntryPos track whether bridge or not
1341  {
1342  TrainInFront = true;
1343  }
1344  else
1345  {
1346  TrainInFront = false;
1347  }
1348  }
1349  }
1351  {
1352  if(ClearToNextSignal(0))
1353  {
1354  StoppedForTrainInFront = false;
1355  TrainInFront = false;
1356  BeingCalledOn = false;
1357  EntrySpeed = 0;
1359  FirstHalfMove = true;
1360  SetTrainMovementValues(16, NextElementPosition, NextEntryPos);
1361  }
1362  }
1363  }
1364  if(Stopped() && TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1365  {
1366  TrainHasFailed(4);
1367  Utilities->CallLogPop(1097);
1368  return;
1369  }
1370  if((Straddle == MidLag) && (LeadElement != -1))
1371  // later check only for Straddle == LeadMid, so need this check here for initial train start
1372  {
1374  }
1375 /* Logic below as follows: This check is made to allow a restart if had StoppedAtLocation or StoppedForTrainInFront or
1376  both but potentially able to restart (i.e. not at buffers, not crashed, not derailed, not held at location, departure
1377  time due, no train in front now & no other stop condition). Note that can be StoppedForTrainInFront when not at a
1378  location since this is set in SetTrainMovementValues whenever a train has zero EntrySpeed and there is a train in front,
1379  which could be when start as Snt.
1380  If StoppedForTrainInFront but not StoppedAtLocation then need to set TRSTime high so pink not plotted, and ReleaseTime
1381  low so can restart if appropriate. BeingCalledOn was set so that when train stopped at a station it wouldn't restart
1382  until the line was clear of trains up to the next signal. Hence check whether BeingCalledOn & if so set
1383  StoppedForTrainInFront, this ensures two things - that the restart check is carried out at each cycle and also that
1384  a restart won't happen until the line is clear to the next signal, regardless of whether or not the ReleaseTime has been
1385  reached.
1386  Then check if TRS time reached & change background to pink if so, & check if release time reached & if so change
1387  background to white and clear StoppedAtLocation. Then make check of station name, and recheck StoppedForTrainInFront,
1388  if it's set check if ClearToNextSignal and if so clear StoppedForTrainInFront & BeingCalledOn. If not ClearToNextSignal
1389  then return. If either not StoppedForTrainInFront or ClearToNextSignal then restart, calling SetTrainMovementValues &
1390  sending a message to the performancelog.
1391 */
1392 
1393  if(TrainMode == Timetable)
1394  {
1396  {
1397 // if(BeingCalledOn) //dropped when added TrainInFront at v2.18.0
1398 // {
1399 // TrainInFront = true;
1400 // }
1401  SetTrainMovementValues(25, LeadElement, LeadEntryPos); //this is purely to set StoppedForTrainInFront as needed before formal call //added at v2.19.1
1402  if((TrainController->TTClockTime >= TRSTime) && (PowerAtRail >= 1) && !StoppedForTrainInFront) //added later conditions at v2.19.1
1403  {
1405  }
1406  else //added at v2.14.0 as if a train ready to depart (pink b'gnd) taken under sig control then restored to tt control b'gnd stayed pink,
1407  { //even though release time now 30 seconds after tt control restored
1409  }
1411  {
1412  // value updated at every scheduled departure & arrival
1413  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0 moved here from after 'StoppedAtLocation = false' at v2.19.1
1414  {
1415  StoppedWithoutPower = true;
1416  }
1417  if(!StoppedWithoutPower && !StoppedForTrainInFront) //added at v2.19.1, don't change colour if no power or train in front
1418  {
1420  }
1421  AnsiString StationName;
1423  {
1425  }
1427  {
1429  }
1430  else
1431  {
1432  throw Exception("Error - Stopped at through station but neither lead nor mid elements have a name");
1433  }
1434  EntrySpeed = 0;
1436  if(!StoppedWithoutPower && !StoppedForTrainInFront) //added at v2.19.1 & all below put in conditional block - due to JasonB false departure log in email of 21/02/24
1437  { //= extended it to StoppedForTrainInFront as well for same reasons
1438  ZeroPowerDepartMessage = false;
1439  TrainInFrontMessage = false;
1440  FirstHalfMove = true;
1441  StoppedAtLocation = false;
1442  if((NextElementPosition > -1) && (NextEntryPos > -1))
1443  // condition check added for SloughIECC error reported by James U
1444  {
1445  if((Track->TrackElementAt(720, NextElementPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal) &&
1446  (Track->TrackElementAt(721, NextElementPosition).Attribute == 0))
1447  {
1448  StoppedAtSignal = true;
1450  // TrainController->LogActionError(40, HeadCode, "", SignalHold, Track->TrackElementAt(755, NextElementPosition).ElementID);
1451  }
1452  }
1454  {
1455  TimeTimeLocArrived = false;
1456  LogAction(27, HeadCode, "", Depart, StationName, "", ActionVectorEntryPtr->DepartureTime, false);
1457  // no warning for TimeTimeLoc departure
1458  }
1459  else if(TreatPassAsTimeLocDeparture) //added at v2.12.0 so late/early/on time mins recorded accurately
1460  {
1461  LogAction(36, HeadCode, "", Depart, StationName, "", ActionVectorEntryPtr->EventTime, ActionVectorEntryPtr->Warning); //EventTime because the real event is a pass
1462  }
1463  else //must be TimeLoc departure
1464  {
1466  }
1467  TreatPassAsTimeLocDeparture = false; //added at v2.12.0, reset after train departs
1468  DepartureTimeSet = false;
1469  // no need to set LastActionTime for a departure
1470  //deal here with departure pointer change, increment if SkippedDeparture
1471  CumulativeDelayedRandMinsOneTrain += DelayedRandMins; //only add these after late mins added (in LogAction)
1472 
1473  if(SkippedDeparture) //only deal with this when have power
1474  {
1477  TrainSkippedEvents = 0;
1478  SkippedDeparture = false;
1479  SkipPtrValue = 0;
1480  ActionsSkippedFlag = false;
1481  }
1482  else
1483  {
1485  }
1486  // advance pointer beyond departure action - (this line (& LogAction) used to be at the end - see
1487  // note
1488 /*
1489  Note: If train stops at station after call on with a TimeTimeLoc loaded, and before the normal stop point, then when
1490  SetTrainMovementValues called it assumes a stop at the stop point because the ActionVectorEntryPtr points to a name
1491  when NameInTimetableBeforeCDT is called and the stop positions are valid. So next element train movement is based on
1492  this calculation. However, when the departure time check is made (it is during this function when SetTrainMovementValues
1493  is called), the ActionVectorEntryPtr is advanced at the end past the departure location, so at the next element when
1494  SetTrainMovementValues is called again, all is normal, i.e. the train doesn't stop again at the location. But to cure
1495  the problem move the ActionVectorEntryPtr increment to before SetTrainMovementValues.
1496 */
1498  {
1499  StoppedAtBuffers = true;
1500  }
1501  else
1502  // if buffers or no power, don't set values
1503  {
1505  {
1506  SetTrainMovementValues(12, NextElementPosition, NextEntryPos);
1507  // NextElement is the element to be entered
1508  }
1509  else
1510  {
1512  // use LeadElement for an exit continuation
1513  }
1514  }
1515  }
1516  else if(StoppedForTrainInFront) // StoppedForTrainInFront, don't advance AVPtr - added at v2.19.1
1517  {
1518  if(!TrainInFrontMessage)
1519  {
1520  TrainController->LogActionError(67, HeadCode, "", FailTrainInFront, StationName);
1521  TrainInFrontMessage = true;
1522  }
1523  }
1524  else //no power, don't advance AVPtr - added at v2.19.1
1525  {
1527  {
1529  ZeroPowerDepartMessage = true;
1530  }
1531  }
1532 
1533  }
1534  }
1535  }
1536  if(Straddle == LeadMidLag) //train on a half element
1537  {
1539  {
1540  Utilities->CallLogPop(654);
1541  return;
1542  }
1543  }
1544  else //train fully on 2 elements
1545  {
1547  {
1548  Utilities->CallLogPop(655);
1549  return;
1550  }
1551  }
1552  if((LeadElement > -1) && (MidElement > -1))
1553  {
1555  {
1556  // don't allow to stop if exiting at a continuation as causes problems if try to change direction
1557  // if entering at continuation & LeadElement is a continuation then MidElement will be -1
1558  //don't need to check for MidElement being continuation because popup menu won't show when exiting at continuation so SignallerStoppingFlag can't be set
1559  SignallerStoppingFlag = false;
1560  StepForwardFlag = false;
1561  }
1562  }
1563  if(Stopped())
1564  // this is what prevents another movement if the train is stopped
1565  {
1566  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1567  {
1568  TrainHasFailed(5);
1569  }
1570  BrakeRate = 0;
1571  Utilities->CallLogPop(656);
1572  return;
1573  }
1574 
1575  // HERE WHEN READY FOR NEXT MOVE
1576 
1577  //added at v2.10.0 to set SPADFlag if red signal immediately ahead (as it will be if in a locked route)
1578  //check if due to run past a red signal & if so set SPADFlag (SetTrainMovementValues & its SPAD check only called when arrive fully on 2 elements)
1579  if(Straddle == LeadMid) //fully on 2 elements
1580  {
1581  if(LeadElement > -1)
1582  {
1583  if(Track->TrackElementAt(1402, LeadElement).Conn[LeadExitPos] > -1)
1584  {
1586  if(TIF.TrackType == SignalPost)
1587  {
1588  int TIFEntryPos = Track->TrackElementAt(1405, LeadElement).ConnLinkPos[LeadExitPos];
1589  int TIFExitPos = 0;
1590  if(TIFEntryPos == 0)
1591  {
1592  TIFExitPos = 1;
1593  }
1594  if((TIF.Config[TIFExitPos] == Signal) && TIF.Attribute == 0 && (ExitSpeedHalf > 1) && !AllowedToPassRedSignal && !TIF.CallingOnSet) //use ExitSpeedHalf as may have been stopped at signal so entryspeed is 0
1596  {
1597  SPADFlag = true; // user has to intervene to reset & restart after spad
1598  }
1599  }
1600  }
1601  }
1602  }
1603 
1604  // check for train in front & if so stop at next access (when train fully on element next to train)
1605  if((TrainMode == Signaller) && (Straddle == LeadMidLag))
1606  // SetTrainMovementValues brakes & stops signaller mode train for a train in front using local
1607  // variable TrainInFrontInSignallerModeFlag
1608  {
1609  if((LeadElement > -1) && (LeadExitPos > -1))
1610  {
1611  int NextPos = Track->TrackElementAt(1672, LeadElement).Conn[LeadExitPos];
1612  int NextEntryPos = Track->TrackElementAt(1675, LeadElement).ConnLinkPos[LeadExitPos];
1613  if(Track->OtherTrainOnTrack(16, NextPos, NextEntryPos, TrainID))
1614  // true if another train on NextEntryPos track whether bridge or not
1615  {
1616  TrainInFront = true;
1617  }
1618  else
1619  {
1620  TrainInFront = false;
1621  }
1622  }
1623  }
1624  if((Straddle == LeadMid) && SPADFlag)
1625  // give message + plot background when ready to move half past the signal
1626  {
1627  if(NextElementPosition > -1)
1628  {
1629  if((Track->TrackElementAt(662, NextElementPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal) &&
1630  (Track->TrackElementAt(663, NextElementPosition).Attribute == 0))
1631  {
1632  AnsiString LocID = AnsiString(Track->TrackElementAt(664, NextElementPosition).ElementID);
1634  // if goes past 2 signals then give message twice
1636  }
1637  }
1638  }
1639  if(Straddle == LeadMidLag)
1640  // During this function train moves fully onto 2 elements, Lead & Mid, so set next 2 moves from here for the element after Lead
1641  {
1642  // if SPADFlag set allow to keep moving until signal obscured before setting background colour, & stop only when ExitSpeedFull is 0
1643  if(SPADFlag)
1644  {
1645  if(ExitSpeedFull == 0)
1646  {
1647  StoppedAfterSPAD = true;
1648  // but don't want to stop until have moved fully onto element, hence stop test is before this check
1649  }
1650  }
1652  {
1653  if(ExitSpeedFull == 0)
1654  {
1655  // only reach here when will stop on LeadMid, because SetTrainMovementValues called after this (i.e. ExitSpeedFull becomes 0 if not 0 now
1656  // after this test), and Straddle == LeadMidLag so not accessed at the half-move point, hence only reached at the full move
1657  // point when the speed is 0. So, colour change won't occur until fully stopped (early in UpdateTrain()), and the log message
1658  // is sent at the right time and once only.
1659  SignallerStopped = true;
1660  // but don't want to stop until have moved fully onto element, hence stop test is before this check
1661  StepForwardFlag = false;
1662  SignallerStoppingFlag = false;
1663  TTrackElement TE;
1664  AnsiString Loc = "";
1665  bool LocNamed = false;
1666  if(LeadElement > -1)
1667  {
1668  TE = Track->TrackElementAt(782, LeadElement);
1669  if(TE.ActiveTrackElementName != "")
1670  {
1671  Loc = TE.ActiveTrackElementName;
1672  LocNamed = true;
1673  }
1674  else
1675  {
1676  Loc = "track element " + TE.ElementID;
1677  }
1678  }
1679  if((MidElement > -1) && !LocNamed)
1680  {
1681  TE = Track->TrackElementAt(783, MidElement);
1682  if(TE.ActiveTrackElementName != "")
1683  {
1684  Loc = TE.ActiveTrackElementName;
1685  LocNamed = true;
1686  }
1687  else if(Loc == "")
1688  {
1689  Loc = "track element " + TE.ElementID;
1690  }
1691  }
1692  if(Loc == "")
1693  {
1694  Loc = "outside railway";
1695  // must have stopped after left at a continuation (because both lead & mid == -1)
1696  }
1697  else
1698  {
1699  Loc = "at " + Loc;
1700  }
1701  LogAction(30, HeadCode, "", SignallerStop, Loc, "", TrainController->TTClockTime, false); // false for warning
1702  }
1703  }
1704  if(LeadElement > -1) // if an exit continuation then not set
1705  {
1706  if((Track->TrackElementAt(202, LeadElement).TrackType != Points) || ((LeadEntryPos != 0) && (LeadEntryPos != 2)))
1707  {
1709  }
1710  else if((Track->TrackElementAt(203, LeadElement).TrackType == Points) && ((LeadEntryPos == 0) || (LeadEntryPos == 2)))
1711  {
1712  if(Track->TrackElementAt(204, LeadElement).Attribute == 0)
1713  {
1714  LeadExitPos = 1;
1715  }
1716  else
1717  {
1718  LeadExitPos = 3;
1719  }
1720  }
1721  NextElementPosition = Track->TrackElementAt(205, LeadElement).Conn[LeadExitPos];
1722  NextEntryPos = Track->TrackElementAt(206, LeadElement).ConnLinkPos[LeadExitPos];
1723  }
1724  else
1725  {
1726  NextElementPosition = -1;
1727  NextEntryPos = -1;
1728  }
1731  FirstHalfMove = true; //will be when finished the move onto 2 elements during this function
1732 
1733  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0
1734  {
1735  StoppedWithoutPower = true;
1736  }
1737  if((NextElementPosition > -1) && (NextEntryPos > -1) && !SPADFlag)
1738  // may be buffers or continuation. SPADFlag added at v2.1.0
1739  // so don't override the SPAD colour & don't set StoppedAtSignal
1740  {
1741  if((Track->TrackElementAt(207, NextElementPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal) &&
1742  (Track->TrackElementAt(208, NextElementPosition).Attribute == 0) && (ExitSpeedFull < 1) && !RevisedStoppedAtLoc())
1743  {
1744  StoppedAtSignal = true;
1745  if(!StoppedWithoutPower)
1746  // leave background as is if no power, but set StoppedAtSignal
1747  {
1749  }
1750  // TrainController->LogActionError(41, HeadCode, "", SignalHold, Track->TrackElementAt(756, NextElementPosition).ElementID);
1751  }
1752  }
1753  if(!Stopped())
1754  {
1755  if((NextElementPosition > -1) && (NextEntryPos > -1))
1756  // may be buffers or continuation (skip SetTrainMovementValues if buffers, if
1757  // a stop element that isn't buffers - e.g. station, then will skip the calcs
1758  // during SetTrainMovementValues to avoid trying to divide by zero - see that
1759  // function for fuller explanation
1760  {
1761  SetTrainMovementValues(8, NextElementPosition, NextEntryPos);
1762  // NextElement is the element to be entered
1763  }
1764  // follow the continuation exits:-
1765  else if((LeadElement > -1) && (Track->TrackElementAt(209, LeadElement).TrackType == Continuation))
1766  {
1768  // Use LeadElement for calcs if lead is a continuation
1769  }
1770  else if((MidElement > -1) && (Track->TrackElementAt(210, MidElement).TrackType == Continuation))
1771  {
1773  // Use MidElement for calcs if mid is a continuation
1774  }
1775  else if((LagElement > -1) && (Track->TrackElementAt(211, LagElement).TrackType == Continuation))
1776  {
1778  // Use LagElement for calcs if lag is a continuation
1779  }
1780  }
1781  // remove route elements if not autosigs - this section moved from below, was under LagElement > -1 condition but needs to cover LagElement == -1
1782  if((AllRoutes->GetRouteTypeAndGraphics(2, LeadElement, LeadEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::NotAutoSigsRoute))
1783  // Trains may not be in a route
1784  // Since Straddle = LeadMidLag at this point the train is going to move fully off the existing Lag & fully onto existing Lead element during this function
1785  {
1786  // NB if LeadElement == -1 then the above test returns NoRoute
1787  int TempH = Track->TrackElementAt(213, LeadElement).HLoc;
1788  int TempV = Track->TrackElementAt(214, LeadElement).VLoc;
1789  int TempELink = Track->TrackElementAt(215, LeadElement).Link[LeadEntryPos];
1790  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
1791  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(10, TempH, TempV, SecondPair);
1792  if((FirstPair.first > -1) && (AllRoutes->GetFixedRouteAt(143, FirstPair.first).GetFixedPrefDirElementAt(153,
1793  FirstPair.second).GetELink() == TempELink))
1794  {
1795  AllRoutes->RemoveRouteElement(10, TempH, TempV, TempELink);
1796  }
1797  else if((SecondPair.first > -1) && (AllRoutes->GetFixedRouteAt(144, SecondPair.first).GetFixedPrefDirElementAt(154,
1798  SecondPair.second).GetELink() == TempELink))
1799  {
1800  AllRoutes->RemoveRouteElement(11, TempH, TempV, TempELink);
1801  }
1802  }
1803  if(AllRoutes->GetRouteTypeAndGraphics(3, MidElement, MidEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::NotAutoSigsRoute)
1804  // Trains may not be in a route
1805  {
1806  int TempH = Track->TrackElementAt(216, MidElement).HLoc;
1807  int TempV = Track->TrackElementAt(217, MidElement).VLoc;
1808  int TempELink = Track->TrackElementAt(218, MidElement).Link[MidEntryPos];
1809  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
1810  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(11, TempH, TempV, SecondPair);
1811  if((FirstPair.first > -1) && (AllRoutes->GetFixedRouteAt(145, FirstPair.first).GetFixedPrefDirElementAt(155,
1812  FirstPair.second).GetELink() == TempELink))
1813  {
1814  AllRoutes->RemoveRouteElement(12, TempH, TempV, TempELink);
1815  }
1816  else if((SecondPair.first > -1) && (AllRoutes->GetFixedRouteAt(146, SecondPair.first).GetFixedPrefDirElementAt(156,
1817  SecondPair.second).GetELink() == TempELink))
1818  {
1819  AllRoutes->RemoveRouteElement(13, TempH, TempV, TempELink);
1820  }
1821  }
1822  if(AllRoutes->GetRouteTypeAndGraphics(4, LagElement, LagEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::NotAutoSigsRoute)
1823  // Trains may not be in a route
1824  {
1825  int TempH = Track->TrackElementAt(219, LagElement).HLoc;
1826  int TempV = Track->TrackElementAt(220, LagElement).VLoc;
1827  int TempELink = Track->TrackElementAt(221, LagElement).Link[LagEntryPos];
1828  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
1829  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(12, TempH, TempV, SecondPair);
1830  if((FirstPair.first > -1) && (AllRoutes->GetFixedRouteAt(147, FirstPair.first).GetFixedPrefDirElementAt(157,
1831  FirstPair.second).GetELink() == TempELink))
1832  {
1833  AllRoutes->RemoveRouteElement(14, TempH, TempV, TempELink);
1834  }
1835  else if((SecondPair.first > -1) && (AllRoutes->GetFixedRouteAt(148, SecondPair.first).GetFixedPrefDirElementAt(158,
1836  SecondPair.second).GetELink() == TempELink))
1837  {
1838  AllRoutes->RemoveRouteElement(15, TempH, TempV, TempELink);
1839  }
1840  AllRoutes->CheckMapAndRoutes(8); // test
1841  }
1842  if(LagElement > -1)
1843  // not entering at a continuation so can deal with train leaving the lag element
1844  {
1846  // amended below so route elements removed for the complete train (for NotAutoSigsRoutes), so train never standing on a route once it
1847  // starts moving, covers for eliminating route when train reaches buffers, and prevents odd route segments when route extended while
1848  // straddling 3 elements (formerly the last segment was replotted as a route & stayed plotted
1849 
1850  TPrefDirElement PrefDirElement;
1851  // plot locked route marker for any element if appropriate (i.e. if a locked AutoSigs route) but only when train leaves element completely
1852  // as this is a 16x16 graphic
1854  {
1856  RailGraphics->LockedRouteCancelPtr[PrefDirElement.GetELink()]);
1857  }
1858  if(ContinuationExit(2, LagElement, LagExitPos)) // true if Element is a continuation and Exitpos is the continuation end
1859  {
1860  int RouteNumber;
1861  TrainGone = true;
1862  // flag to indicate train to be deleted - outside this function
1864  {
1865  TTrainController::TContinuationAutoSigEntry ContinuationAutoSigEntry;
1866  ContinuationAutoSigEntry.RouteNumber = RouteNumber;
1867  // calc distance from & inc last signal to exit
1868  int LastElement = LagElement, LastExitPos = LagExitPos, CumDistance = 0;
1869  int NewLastElement = 0, NewLastExitPos = 0;
1870  // need above because can't change LastElement & LastExitPos until both new values obtained
1871  // while((Track->TrackElementAt(684, LastElement).Config[LastExitPos] != Signal) && (CumDistance < 1200)) as was
1872  while((Track->TrackElementAt(913, LastElement).Config[LastExitPos] != Signal) && (CumDistance < 1200) && (Track->TrackElementAt(897,
1873  LastElement).TrackType != Points))
1874  // extra condition above added because of Moric1998's error (see email of 24/03/2016), where had an autosigs route across points, and another continuation on track not occupied by route so
1875  // failed when found a new element = -1 when tried to cross the continuation. Note this routine can only deal with non points as it uses GetNonPointsOppositeLinkPos
1876  // leave CumDistance as it was in these circumstances.
1877  {
1878  if(LastExitPos < 2)
1879  {
1880  CumDistance += Track->TrackElementAt(685, LastElement).Length01;
1881  }
1882  else
1883  {
1884  CumDistance += Track->TrackElementAt(686, LastElement).Length23;
1885  }
1886  NewLastElement = Track->TrackElementAt(687, LastElement).Conn[Track->GetNonPointsOppositeLinkPos(LastExitPos)];
1887  if(NewLastElement == -1)
1888  // this will catch buffers or any other connection failure
1889  {
1890  break; //throw Exception("Error, Connection = -1 in Continuation loop in UpdateTrain"); //dropped at v2.15.0 because of Brent Mackie's error file of
1891  } //10/02/23, had two continuations linked with no signal between
1892  NewLastExitPos = Track->TrackElementAt(688, LastElement).ConnLinkPos[Track->GetNonPointsOppositeLinkPos(LastExitPos)]; //so when train exited this routine tracked
1893  if(NewLastExitPos == -1) //back to the entry continuation which had no further connection - doesn't need to be an error at all!
1894  {
1895  break; //throw Exception("Error, ConnLinkPos = -1 in Continuation loop in UpdateTrain"); //dropped at v2.15.0 because of Brent Mackie's error file of 10/02/23 , see above
1896  }
1897  LastElement = NewLastElement;
1898  LastExitPos = NewLastExitPos;
1899  }
1900  // if at signal add this in too (may not be signal if 'break;' encountered but doesn't matter)
1901  if(CumDistance < 1200)
1902  {
1903  CumDistance += Track->TrackElementAt(689, LastElement).Length01; // only need 01 for signal
1904  }
1905  // now have distance including the signal, if >=1200m use 100m (for a signal immediately after the continuation)
1906  // else use 1200m - CumDistance
1907  int FirstDistance = 0;
1908  if(CumDistance >= 1200)
1909  {
1910  FirstDistance = 100;
1911  }
1912  else
1913  {
1914  FirstDistance = 1200 - CumDistance;
1915  }
1916  if(FirstDistance < 100)
1917  {
1918  FirstDistance = 100; // don't allow < 100
1919  }
1920  // can now calc the time delays in seconds - FirstDelay, SecondDelay & ThirdDelay, these are doubles
1921  // BUT - first check whether ExitSpeedFull is very low (Mark had divide by zero error with zero exit speed using v2.4.0)
1922  if(ExitSpeedFull > 20.0)
1923  {
1924  ContinuationAutoSigEntry.FirstDelay = 3.6 * double(FirstDistance) / ExitSpeedFull;
1925  // speed in km/h & distance in m so mult by 3.6 to bring to secs
1926  ContinuationAutoSigEntry.SecondDelay = ContinuationAutoSigEntry.FirstDelay + 4320.0 / ExitSpeedFull;
1927  // 4320.0 = 3.6 * 1200, .0 to make it a double
1928  ContinuationAutoSigEntry.ThirdDelay = ContinuationAutoSigEntry.SecondDelay + 4320.0 / ExitSpeedFull;
1929  }
1930  else
1931  {
1932  ContinuationAutoSigEntry.FirstDelay = 60.0; // 60 secs between each action
1933  ContinuationAutoSigEntry.SecondDelay = 120.0;
1934  ContinuationAutoSigEntry.ThirdDelay = 180.0;
1935  }
1936  ContinuationAutoSigEntry.AccessNumber = 0;
1937  ContinuationAutoSigEntry.PassoutTime = TrainController->TTClockTime;
1939  {
1941  for(VectorIT = TrainController->ContinuationAutoSigVector.begin(); VectorIT != TrainController->ContinuationAutoSigVector.end();
1942  VectorIT++)
1943  {
1944  if(VectorIT->RouteNumber == RouteNumber)
1945  {
1946  // another train has passed out of same route so erase earlier entry
1947  TrainController->ContinuationAutoSigVector.erase(VectorIT);
1948  break;
1949  }
1950  }
1951  }
1952  TrainController->ContinuationAutoSigVector.push_back(ContinuationAutoSigEntry);
1953  }
1955  // need to plot this as returning early so will miss the later plot (not a bridge so don't need PlotAlternativeTrackRouteGraphic)
1956  Display->Update();
1957  // need to keep this since Update() not called for PlotSmallOutput as too slow
1958  Utilities->CallLogPop(659);
1959  return;
1960  }
1961  // above covers for exiting at continuation, need XLinkPos check to exclude entering at a continuation
1962  if(LeadElement > -1)
1963  {
1964  TTrackElement &TE = Track->TrackElementAt(224, LeadElement); //added at v2.13.0 for brevity
1965  if(TE.Config[LeadExitPos] == Signal)
1966  // changed to lead so reset early
1967  {
1968  LastSigPassedFailed = false; //used to cancel route elements up to next signal for autosigs route
1969  TE.Attribute = 0; // red
1970  int RouteNumber; //only used for autosigs routes
1971  //add chance to fail when train passes a signal
1972  if((random(Utilities->SignalChangeEventsPerFailure) == 0) && !TE.Failed && (Utilities->FailureMode != FNil) &&
1973  (TrainMode == Timetable) && !TE.CallingOnSet) //can't fail twice, calling on signal can't fail
1974  {
1976  IFE.TVPos = LeadElement;
1977  TE.Failed = true;
1978  Display->WarningLog(19, Utilities->Format96HHMMSS(TrainController->TTClockTime) + ": Signal failed at " + TE.ElementID);
1979  PerfLogForm->PerformanceLog(42, Utilities->Format96HHMMSS(TrainController->TTClockTime) + " WARNING: Signal failed at " + TE.ElementID);
1980  TrainController->StopTTClockMessage(129, "Signal at " + TE.ElementID +
1981  " failed when changing aspect.\nTrains can only pass under signaller control.");
1982  AllRoutes->RebuildRailwayFlag = true; //force ClearandRebuildRailway at next clock tick
1983  LastSigPassedFailed = true;
1984  //set repair time, random value in minutes between 10 and 179
1985  double FailureMinutes = double(random(Utilities->MaxRandomRepairTime) + Utilities->FixedMinRepairTime); //between 10 and 179 minutes at random
1986  TDateTime RepairTime = TrainController->TTClockTime + TDateTime(FailureMinutes / 1440);
1987  IFE.RepairTime = RepairTime;
1989  Track->FailedSignalsVector.push_back(IFE); //rearwards signals will be set when LagElement leaves signal
1990  }
1991  TE.CallingOnSet = false;
1992  // don't plot if zoomed out
1993  if(!Display->ZoomOutFlag)
1994  {
1996  }
1997  // covers signal resetting in same direction
1998  }
1999  }
2001  {
2002  AllRoutes->RebuildRailwayFlag = true; //added at v2.13.0 to replot signal after train left in case it had failed
2003  if(AllRoutes->GetRouteTypeAndGraphics(5, LagElement, LagEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::AutoSigsRoute)
2004  {
2005  Display->PlotOutput(23, Track->TrackElementAt(227, LagElement).HLoc * 16, Track->TrackElementAt(228, LagElement).VLoc * 16, EXGraphicPtr);
2006  Display->PlotOutput(24, Track->TrackElementAt(229, LagElement).HLoc * 16, Track->TrackElementAt(230, LagElement).VLoc * 16, EntryDirectionGraphicPtr);
2007  TPrefDirElement PrefDirElement;
2008  // plot locked route marker for same side signal if appropriate (may be covered above but leave in), but only when train leaves element completely as this is a 16x16 graphic
2010  {
2012  RailGraphics->LockedRouteCancelPtr[PrefDirElement.GetELink()]);
2013  }
2015  LockedVectorNumber)))
2016  {
2018  }
2019  }
2020  }
2021  else if((LeadElement > -1) && (Track->TrackElementAt(233, LeadElement).TrackType == SignalPost))
2022  {
2023  Track->TrackElementAt(234, LeadElement).Attribute = 0; // red
2025  // don't plot if zoomed out
2026  if(!Display->ZoomOutFlag)
2027  {
2029  }
2030  // covers signal passed in opposite direction - replot as red, regardless of what it was before, though should already have been red
2031  }
2033  {
2034  if(AllRoutes->GetRouteTypeAndGraphics(6, LagElement, LagEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::AutoSigsRoute)
2035  {
2036  Display->PlotOutput(26, Track->TrackElementAt(236, LagElement).HLoc * 16, Track->TrackElementAt(237, LagElement).VLoc * 16, EXGraphicPtr);
2037  Display->PlotOutput(27, Track->TrackElementAt(238, LagElement).HLoc * 16, Track->TrackElementAt(239, LagElement).VLoc * 16, EntryDirectionGraphicPtr);
2038  // below added at v1.3.0 to reset signals if back out of an autosigs route under signaller control after changing direction, when new LeadElement not on route (if it had
2039  // been the route would have been ForceCancelled). Note that the signal is not facing the direction of travel else would have entered
2040  // "if(Track->TrackElementAt(, LagElement).Config[LagExitPos] == Signal)" above and wouldn't be here
2041  int RouteNumber;
2043  // already know it's an autosigsroute, this is just to get the RouteNumber
2044  // addition below at v1.3.2 - found that a signal that had reached double yellow in ContinuationAutoSigs was reset to red when a following train's lag element
2045  // moved off a signal in the normal course of events. It was caused when a train backed out of an autosigs route under signaller control after changing
2046  // direction (see DevHistory.txt). Hence check that the train is in signaller mode and that the train's lead element isn't on the same route before calling SetRouteSignals.
2047  int RouteNumber2;
2049  // already know it's an autosigsroute, this is just to get the RouteNumber
2050  if((TrainMode == Signaller) && (RouteNumber2 != RouteNumber))
2051  // note that if not in a route (as likely) then RouteNumber2 set to -1
2052  {
2053  AllRoutes->GetFixedRouteAt(217, RouteNumber).SetRouteSignals(10);
2054  // this was in the 1.3.0 addition but without the condition
2055  }
2056  // end of 1.3.2 addition
2057  // end of 1.3.0.addition
2058  }
2059  TPrefDirElement PrefDirElement;
2060  // plot locked route marker for opp side signal if appropriate (may be covered above but leave in), but only when train leaves element completely as this is a 16x16 graphic (OK - Straddle == LeadMidLag)
2062  {
2064  RailGraphics->LockedRouteCancelPtr[PrefDirElement.GetELink()]);
2065  }
2066  }
2067  }
2068  }
2069  // straddle ONLY changed here, check if 'LeadMid' first & if so ready for updating Elements
2070  if(Straddle == LeadMid) //about to move half onto next element
2071  {
2072  AllowedToPassRedSignal = false;
2073  // if had been allowed to pass then at this point it will move half onto signal so can be reset
2074  // if(LagElement > -1) ResetTrainElementID(LagElement, LagEntryPos);//train fully off old LagElement so can clear TrainOnElement flags - no, reset at earlier call when lag moves off element
2075  if(DerailPending)
2076  // set during last GetLeadElement, but only act on it when train fully on offending point
2077  // i.e. next time Straddle reaches LeadMid
2078  {
2079  Derailed = true;
2080  DerailPending = false;
2084  Utilities->CallLogPop(657);
2085  return;
2086  }
2093  Straddle = MidLag;
2094  // train now fully on the updated Lag & Mid, the front segment is going to move onto the new
2095  // LeadElement during this function (note that if stopped at signal then won't get this far)
2096  if(LeadElement > -1)
2097  {
2099  // i.e an exit continuation only
2100  // if don't exclude entry continuations then can't progress past it
2101  {
2102  LeadElement = -1;
2103  }
2104  else
2105  {
2106  GetLeadElement(0);
2107  // sets or resets DerailPending & StoppedAtSignal, and sets LeadElement values
2109  if(Stopped())
2110  {
2111  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
2112  {
2113  TrainHasFailed(6);
2114  }
2115  Utilities->CallLogPop(658);
2116  return; // i.e. don't move forward one step if next element is a red signal
2117  }
2118  }
2119  }
2120  }
2121  if(LagElement > -1)
2122  {
2123  // below are the actions required at both half moves for LagElement > -1
2125 
2126  // if was in locked route but has timed out when train leaves then plot the normal track graphic over the route graphic that is
2127  // still in BackgroundGraphic[3], if wasn't in a route then will just replot the same BackgroundGraphic
2128  // need to do this for each half element
2129 
2130  TPrefDirElement PrefDirElement;
2131  if(!(AllRoutes->IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber(7, LagElement, LagExitPos, PrefDirElement, LockedVectorNumber)))
2132  {
2133  int RouteNumber; // holder for call below - not used
2135  {
2136  if(Utilities->clTransparent == TColor(0xFFFFFF))
2137  // change to black for a white background
2138  {
2140  // only applies for AutoSigs Route in case was locked & timed out
2141  }
2142  else
2143  // change to white for a dark background
2144  {
2146  // only applies for AutoSigs Route in case was locked & timed out
2147  }
2149  }
2150  }
2152  // above in case train just moving off a bridge & either alternative track in a route - need to keep its route colour,
2153  // or a train on the opposite track - needs to be replotted
2154  }
2155  // update all array values
2156  HOffset[3] = HOffset[2];
2157  HOffset[2] = HOffset[1];
2158  HOffset[1] = HOffset[0];
2159  VOffset[3] = VOffset[2];
2160  VOffset[2] = VOffset[1];
2161  VOffset[1] = VOffset[0];
2162  Graphics::TBitmap *TempPtr = BackgroundPtr[3];
2163 
2164  BackgroundPtr[3] = BackgroundPtr[2];
2165  BackgroundPtr[2] = BackgroundPtr[1];
2166  BackgroundPtr[1] = BackgroundPtr[0];
2167  BackgroundPtr[0] = TempPtr;
2168 
2169  // update headcode graphics depending on Lead entry value
2170  if(LeadElement > -1) // if Lead is -1 then stays as is
2171  {
2173  {
2174  for(int x = 0; x < 4; x++)
2175  {
2176  HeadCodePosition[x] = HeadCodeGrPtr[3 - x];
2177  }
2178  }
2179  else
2180  {
2181  for(int x = 0; x < 4; x++)
2182  {
2184  }
2185  }
2186  }
2187  if(TrainMode == Timetable)
2188  {
2190  }
2191  else
2192  {
2194  }
2196 
2197  // plot new seg [0] on Lead & [2] on Mid ([2] always on Mid)
2198  if(LeadElement > -1)
2199  {
2200  if(Straddle == MidLag)
2201  // just about to move half onto the new lead element
2202  {
2204  // pick up new background bitmap [0]
2206  int LeadElementTrainID = Track->TrackElementAt(244, LeadElement).TrainIDOnElement;
2207  if((LeadElementTrainID > -1) && (LeadElementTrainID != TrainID))
2208  // check if own ID for entry at continuation, else crashes into itself!
2209  {
2210  // OK if crossing on a bridge
2211  int OtherTrainEntryPos = TrainController->EntryPos(0, LeadElementTrainID, LeadElement);
2212  if(OtherTrainEntryPos == -1)
2213  {
2214  throw Exception("Error - OtherTrainEntryPos not set");
2215  }
2216  if((Track->TrackElementAt(246, LeadElement).TrackType != Bridge) || (LeadEntryPos == OtherTrainEntryPos) ||
2217  // LeadEntryPos for rear end crashes
2218  (LeadExitPos == OtherTrainEntryPos))
2219  // LeadExitPos for head-on crashes
2220  {
2222  Crashed = true; // only set if Straddle = MidLag
2223  CallingOnFlag = false;
2224  // in case was set, need to disable call on if call on button had been pressed
2225  }
2226  }
2227  else if(MidElement > -1) // will be -1 for continuation entries
2228  {
2229  // check if about to move onto a crossing diagonal that is occupied by another train, and if so crash
2230  int MidExitLinkNum = Track->TrackElementAt(889, MidElement).Link[MidExitPos];
2231  int MidHLoc = Track->TrackElementAt(890, MidElement).HLoc;
2232  int MidVLoc = Track->TrackElementAt(891, MidElement).VLoc;
2233  int OtherTrainID = -1;
2234  if((MidExitLinkNum == 1) || (MidExitLinkNum == 3) || (MidExitLinkNum == 7) || (MidExitLinkNum == 9))
2235  {
2236  if(Track->DiagonalFouledByTrain(0, MidHLoc, MidVLoc, MidExitLinkNum, OtherTrainID))
2237  {
2238  TrainCrashedInto = OtherTrainID;
2239  Crashed = true; // only set if Straddle = MidLag
2240  CallingOnFlag = false;
2241  // in case was set, need to disable call on if call on button had been pressed
2242  }
2243  }
2244  }
2245  }
2246  else
2247  {
2249  // pick up new background bitmap [0]
2251  }
2252  PlotElement[0] = LeadElement;
2254  PlotTrainGraphic(12, 0, Display);
2255  }
2256  if(MidElement > -1)
2257  {
2258  PlotElement[2] = MidElement;
2260  PlotTrainGraphic(1, 2, Display);
2261  }
2262  // plot the new positions for [1] & [3] graphics - [1] on Mid if Straddle = MidLag, on Lead if Straddle = LeadMidLag
2263  // [3] on Lag if Straddle = MidLag, on Mid if Straddle = LeadMidLag
2264  if(Straddle == MidLag)
2265  {
2266  if(MidElement > -1)
2267  {
2268  PlotElement[1] = MidElement;
2270  PlotTrainGraphic(2, 1, Display);
2271  }
2272  if(LagElement > -1)
2273  {
2274  PlotElement[3] = LagElement;
2276  PlotTrainGraphic(3, 3, Display);
2277  }
2278  }
2279  else // Straddle == LeadMidLag
2280  {
2281  if(LeadElement > -1)
2282  {
2283  PlotElement[1] = LeadElement;
2285  PlotTrainGraphic(4, 1, Display);
2286  }
2287  if(MidElement > -1)
2288  {
2289  PlotElement[3] = MidElement;
2291  PlotTrainGraphic(5, 3, Display);
2292  }
2293  }
2294  if(Crashed)
2295  // only reach here if crash into another train, if crash into buffers or an LC then return earlier at the if(Stopped()) test
2296  {
2301  // in case was set, need to disable call on if call on button had been pressed
2308  Straddle = LeadMidLag;
2309  // was MidLag but plotted as LeadMidLag so change Straddle accordingly
2310  Display->Update();
2311  // resurrected when Update() dropped from PlotOutput etc
2312  Utilities->CallLogPop(660);
2313  return;
2314  }
2315  // deal here with station stops & pass times after all replotting done but before Straddle changed
2316  if(TrainMode == Timetable)
2317  {
2318  if(Straddle == LeadMidLag)
2319  {
2320  if((LeadElement > -1) && (MidElement > -1) && !TimetableFinished)
2321  {
2322  // NameInTimetableBeforeCDT returns the number by which the train ActionVectorEntryPtr needs to be incremented
2323  // to point to the location arrival entry - before a change of direction
2324  AnsiString LocName = Track->TrackElementAt(249, LeadElement).ActiveTrackElementName;
2325  bool StopRequired = false;
2326  int TTVPos = NameInTimetableBeforeCDT(1, LocName, StopRequired); //excludes continuations
2327  if(TTVPos > -1) // -1 if can't find it or if name is ""
2328  {
2329  // check if at buffers (no, dropped buffer check to allow to crash into buffers) or a through station stop,
2330  // or a station where next element contains a train or a stop signal, if so
2331  // stop now, note that for 2nd check, if next element is a bridge then will have stopped by now so no need
2332  // to test the actual track the train is on since it can't be a platform
2333  TTrackElement LeadTrackElement = Track->TrackElementAt(258, LeadElement);
2334  TTrackElement NextTrackElement; // default for now
2335  bool TrainAtStopLinkPos1 = (LeadTrackElement.StationEntryStopLinkPos1 == LeadEntryPos);
2336  bool TrainAtStopLinkPos2 = (LeadTrackElement.StationEntryStopLinkPos2 == LeadEntryPos);
2337  bool TrainAtStopLinkPos3 = (LeadTrackElement.StationEntryStopLinkPos3 == LeadEntryPos);
2338  bool TrainAtStopLinkPos4 = (LeadTrackElement.StationEntryStopLinkPos4 == LeadEntryPos);
2339  bool ForwardConnection = (LeadTrackElement.Conn[LeadExitPos] > -1);
2340  int NextElementEntryPos = -1;
2341  int NextElementExitPos = -1;
2342  bool TrainOnNextElement = false;
2343  bool StopSignalAtNextElement = false;
2344  if(ForwardConnection)
2345  // if no forward connection can't derive anything from it without errors
2346  {
2347  NextTrackElement = Track->TrackElementAt(262, LeadTrackElement.Conn[LeadExitPos]);
2348  NextElementEntryPos = LeadTrackElement.ConnLinkPos[LeadExitPos];
2349  NextElementExitPos = Track->GetNonPointsOppositeLinkPos(NextElementEntryPos);
2350  // this is only for signals so no need to worry about points ambiguity
2351  TrainOnNextElement = (NextTrackElement.TrainIDOnElement > -1);
2352  StopSignalAtNextElement = ((NextTrackElement.Config[NextElementExitPos] == Signal) && (NextTrackElement.Attribute == 0));
2353  }
2354  if(TrainAtStopLinkPos1 || TrainAtStopLinkPos2 || TrainAtStopLinkPos3 || TrainAtStopLinkPos4 || (ForwardConnection && (TrainOnNextElement || StopSignalAtNextElement)))
2355  {
2356  if(TTVPos > 0)
2357  {
2359  ActionVectorEntryPtr += TTVPos;
2360  }
2361  if(StopRequired)
2362  {
2363  StoppedAtLocation = true;
2364  StoppedAtSignal = false;
2365  // may have been set earlier at line 925 so need to reset as
2366  // StoppedAtLocation takes precedence and don't want both set at same time or have flashing graphic
2367  // in zoom out mode
2368  if(!TrainFailed)
2369  {
2371  // pale green
2372  }
2374  ActualArrivalTime = TrainController->TTClockTime; //added at v2.13.0
2376  {
2377  TimeTimeLocArrived = true;
2378  // used in case of later signaller control, when need to know
2379  // whether had arrived or not, to avoid sending the arrival
2380  // message twice, see TInterface::TimetableControl1Click
2381  }
2382  }
2383  else
2384  {
2386  }
2388  {
2390  }
2391  // don't alter ActionVectorEntryPtr if at a TimeTimeLoc (& can't be anything else other than TimeLoc or PassTime after calling NameInTimetableBeforeCDT successfully)
2393  }
2394  }
2395  }
2396  }
2397  }
2398  if(Straddle == MidLag)
2399  {
2400  Straddle = LeadMidLag;
2401  FirstHalfMove = false;
2402  }
2403  else if(Straddle == LeadMidLag)
2404  {
2405  Straddle = LeadMid;
2406  FirstHalfMove = true;
2407  }
2408  else if(Straddle == LeadMid)
2409  {
2410  throw Exception("Error, Straddle shouldn't be LeadMid prior to resetting at exit from UpdateTrain");
2411  }
2412  if(TrainFailurePending) // ok, moving but PlotElements set above
2413  {
2414  TrainHasFailed(7);
2415  }
2416  Display->Update();
2417  // need to keep this since Update() not called for PlotSmallOutput as too slow
2418  Utilities->CallLogPop(661);
2419 }
2420 
2421 // ----------------------------------------------------------------------------
2422 
2423 Graphics::TBitmap *TTrain::SetOneGraphicCode(char CodeChar)
2424 {
2425  switch(CodeChar)
2426  {
2427  case '0':
2428  return(RailGraphics->Code0);
2429 
2430  case '1':
2431  return(RailGraphics->Code1);
2432 
2433  case '2':
2434  return(RailGraphics->Code2);
2435 
2436  case '3':
2437  return(RailGraphics->Code3);
2438 
2439  case '4':
2440  return(RailGraphics->Code4);
2441 
2442  case '5':
2443  return(RailGraphics->Code5);
2444 
2445  case '6':
2446  return(RailGraphics->Code6);
2447 
2448  case '7':
2449  return(RailGraphics->Code7);
2450 
2451  case '8':
2452  return(RailGraphics->Code8);
2453 
2454  case '9':
2455  return(RailGraphics->Code9);
2456 
2457  case 'A':
2458  return(RailGraphics->CodeA);
2459 
2460  case 'B':
2461  return(RailGraphics->CodeB);
2462 
2463  case 'C':
2464  return(RailGraphics->CodeC);
2465 
2466  case 'D':
2467  return(RailGraphics->CodeD);
2468 
2469  case 'E':
2470  return(RailGraphics->CodeE);
2471 
2472  case 'F':
2473  return(RailGraphics->CodeF);
2474 
2475  case 'G':
2476  return(RailGraphics->CodeG);
2477 
2478  case 'H':
2479  return(RailGraphics->CodeH);
2480 
2481  case 'I':
2482  return(RailGraphics->CodeI);
2483 
2484  case 'J':
2485  return(RailGraphics->CodeJ);
2486 
2487  case 'K':
2488  return(RailGraphics->CodeK);
2489 
2490  case 'L':
2491  return(RailGraphics->CodeL);
2492 
2493  case 'M':
2494  return(RailGraphics->CodeM);
2495 
2496  case 'N':
2497  return(RailGraphics->CodeN);
2498 
2499  case 'O':
2500  return(RailGraphics->CodeO);
2501 
2502  case 'P':
2503  return(RailGraphics->CodeP);
2504 
2505  case 'Q':
2506  return(RailGraphics->CodeQ);
2507 
2508  case 'R':
2509  return(RailGraphics->CodeR);
2510 
2511  case 'S':
2512  return(RailGraphics->CodeS);
2513 
2514  case 'T':
2515  return(RailGraphics->CodeT);
2516 
2517  case 'U':
2518  return(RailGraphics->CodeU);
2519 
2520  case 'V':
2521  return(RailGraphics->CodeV);
2522 
2523  case 'W':
2524  return(RailGraphics->CodeW);
2525 
2526  case 'X':
2527  return(RailGraphics->CodeX);
2528 
2529  case 'Y':
2530  return(RailGraphics->CodeY);
2531 
2532  case 'Z':
2533  return(RailGraphics->CodeZ);
2534 
2535  case 'a':
2536  return(RailGraphics->Code_a);
2537 
2538  case 'b':
2539  return(RailGraphics->Code_b);
2540 
2541  case 'c':
2542  return(RailGraphics->Code_c);
2543 
2544  case 'd':
2545  return(RailGraphics->Code_d);
2546 
2547  case 'e':
2548  return(RailGraphics->Code_e);
2549 
2550  case 'f':
2551  return(RailGraphics->Code_f);
2552 
2553  case 'g':
2554  return(RailGraphics->Code_g);
2555 
2556  case 'h':
2557  return(RailGraphics->Code_h);
2558 
2559  case 'i':
2560  return(RailGraphics->Code_i);
2561 
2562  case 'j':
2563  return(RailGraphics->Code_j);
2564 
2565  case 'k':
2566  return(RailGraphics->Code_k);
2567 
2568  case 'l':
2569  return(RailGraphics->Code_l);
2570 
2571  case 'm':
2572  return(RailGraphics->Code_m);
2573 
2574  case 'n':
2575  return(RailGraphics->Code_n);
2576 
2577  case 'o':
2578  return(RailGraphics->Code_o);
2579 
2580  case 'p':
2581  return(RailGraphics->Code_p);
2582 
2583  case 'q':
2584  return(RailGraphics->Code_q);
2585 
2586  case 'r':
2587  return(RailGraphics->Code_r);
2588 
2589  case 's':
2590  return(RailGraphics->Code_s);
2591 
2592  case 't':
2593  return(RailGraphics->Code_t);
2594 
2595  case 'u':
2596  return(RailGraphics->Code_u);
2597 
2598  case 'v':
2599  return(RailGraphics->Code_v);
2600 
2601  case 'w':
2602  return(RailGraphics->Code_w);
2603 
2604  case 'x':
2605  return(RailGraphics->Code_x);
2606 
2607  case 'y':
2608  return(RailGraphics->Code_y);
2609 
2610  case 'z':
2611  return(RailGraphics->Code_z);
2612 
2613  default:
2614  return(RailGraphics->TempHeadCode);
2615  }
2616 }
2617 
2618 // ----------------------------------------------------------------------------
2619 
2620 void TTrain::SetHeadCodeGraphics(int Caller, AnsiString Code)
2621 {
2622  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SetHeadCodeGraphics," + HeadCode);
2623  if(Code.Length() != 4)
2624  {
2625  TrainController->StopTTClockMessage(62, "Headcode Incorrect length");
2626  }
2627  for(int x = 1; x < 5; x++) // AnsiString indices start at 1
2628  {
2629  HeadCodeGrPtr[x - 1]->Assign(SetOneGraphicCode(Code[x]));
2630  }
2631  if(BackgroundColour != clB5G5R5)
2632  // i.e. not the basic graphic colour as loaded from resource file
2633  {
2634  for(int x = 0; x < 4; x++)
2635  {
2637  }
2638  }
2639  Utilities->CallLogPop(1484);
2640 }
2641 
2642 // ----------------------------------------------------------------------------
2643 
2644 void TTrain::GetLeadElement(int Caller)
2645 // assumes Mid & Lag already set, sets LeadElement,
2646 // LeadEntryPos, LeadExitPos & DerailPending (don't want to act on it immediately)
2647 {
2648  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetLeadElement," + HeadCode);
2649  DerailPending = false;
2653  {
2654  // attr 0=straight, - links 0 & 1 (0 = lead)
2655  // attr 1=diverging, - links 2 & 3 (2 = lead)
2656  // set appropriate next element or derail - use a subroutine & return element & bool for derail
2657  // points always have links 0 & 2 = lead, link 1 = trailing straight, link 3 = training diverging
2658 
2659  // if enter at lead, exit at whatever attr set at
2660  // if enter at lag, exit at lead, but set derail wrt attribute
2661  if((LeadEntryPos == 0) && (Track->TrackElementAt(272, LeadElement).Attribute == 0))
2662  {
2663  LeadExitPos = 1;
2664  }
2665 
2666  // strictly speaking shouldn't need to set to 0 and 2 correctly since TrackIsInARoute caters for both, but
2667  // best to be on safe side
2668  else if(LeadEntryPos == 0)
2669  {
2670  LeadEntryPos = 2;
2671  LeadExitPos = 3;
2672  }
2673  else if((LeadEntryPos == 2) && (Track->TrackElementAt(273, LeadElement).Attribute == 0))
2674  {
2675  LeadEntryPos = 0;
2676  LeadExitPos = 1;
2677  }
2678  else if(LeadEntryPos == 2)
2679  {
2680  LeadExitPos = 3;
2681  }
2682  else if((LeadEntryPos == 1) && (Track->TrackElementAt(274, LeadElement).Attribute == 0))
2683  {
2684  LeadExitPos = 0;
2685  }
2686  else if(LeadEntryPos == 1)
2687  {
2688  LeadExitPos = 0;
2689  DerailPending = true;
2690  }
2691  else if((LeadEntryPos == 3) && (Track->TrackElementAt(275, LeadElement).Attribute == 0))
2692  {
2693  LeadExitPos = 0;
2694  DerailPending = true;
2695  }
2696  else if(LeadEntryPos == 3)
2697  {
2698  LeadExitPos = 0;
2699  }
2700  }
2701  else if(LeadEntryPos == 0)
2702  {
2703  LeadExitPos = 1;
2704  }
2705  else if(LeadEntryPos == 1)
2706  {
2707  LeadExitPos = 0;
2708  }
2709  else if(LeadEntryPos == 2)
2710  {
2711  LeadExitPos = 3;
2712  }
2713  else if(LeadEntryPos == 3)
2714  {
2715  LeadExitPos = 2;
2716  }
2717  // TTrackElement TrackElement = Track->TrackElementAt(276, LeadElement);
2718 /* signal check moved to Update() function
2719  if((TrackElement.TrackType == SignalPost) && (TrackElement.Config[LeadExitPos] == Signal)
2720  && (TrackElement.Attribute == 0))//0 = red
2721  {
2722  StoppedAtSignal = true; //comment out for test of locked route graphic replot
2723  }
2724  else
2725  {
2726  StoppedAtSignal = false;
2727  }
2728 */
2729  Utilities->CallLogPop(662);
2730 }
2731 
2732 // ----------------------------------------------------------------------------
2733 
2734 void TTrain::GetOffsetValues(int Caller, int &HOffset, int &VOffset, int Link) const
2735 {
2736  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetOffsetValues," + AnsiString(Link) + "," + HeadCode);
2737  switch(Link)
2738  {
2739  case 1:
2740  {
2741  HOffset = 0;
2742  VOffset = 0;
2743  break;
2744  }
2745 
2746  case 2:
2747  {
2748  HOffset = 4;
2749  VOffset = 0;
2750  break;
2751  }
2752 
2753  case 3:
2754  {
2755  HOffset = 8;
2756  VOffset = 0;
2757  break;
2758  }
2759 
2760  case 4:
2761  {
2762  HOffset = 0;
2763  VOffset = 4;
2764  break;
2765  }
2766 
2767  case 6:
2768  {
2769  HOffset = 8;
2770  VOffset = 4;
2771  break;
2772  }
2773 
2774  case 7:
2775  {
2776  HOffset = 0;
2777  VOffset = 8;
2778  break;
2779  }
2780 
2781  case 8:
2782  {
2783  HOffset = 4;
2784  VOffset = 8;
2785  break;
2786  }
2787 
2788  case 9:
2789  {
2790  HOffset = 8;
2791  VOffset = 8;
2792  break;
2793  }
2794 
2795  default:
2796  {
2797  throw Exception("Error in GetOffsetValues - Link value wrong");
2798  }
2799  }
2800  Utilities->CallLogPop(674);
2801 }
2802 
2803 // ---------------------------------------------------------------------------
2804 
2805 bool TTrain::LowEntryValue(int EntryLink) const
2806 {
2807 /* returns true if EntryLink is 1, 2, 4 or 7, in these circumstances the front of the train (i.e.
2808  the character that is red or blue) is the last character of the headcode, otherwise it's the first character of the headcode
2809 */
2810  if((EntryLink == 1) || (EntryLink == 2) || (EntryLink == 4) || (EntryLink == 7))
2811  {
2812  return(true);
2813  }
2814  else
2815  {
2816  return(false);
2817  }
2818 }
2819 
2820 // ---------------------------------------------------------------------------
2821 
2822 void TTrain::PickUpBackgroundBitmap(int Caller, int HOffset, int VOffset, int Element, int EntryPos, Graphics::TBitmap *GraphicPtr) const
2823 {
2824  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PickUpBackgroundBitmap," + AnsiString(HOffset) + "," +
2825  AnsiString(VOffset) + "," + AnsiString(Element) + "," + AnsiString(EntryPos) + "," + HeadCode);
2826  Graphics::TBitmap *EXGraphicPtr = RailGraphics->bmTransparentBgnd;
2827  // default values
2828  Graphics::TBitmap *EntryDirectionGraphicPtr = RailGraphics->bmTransparentBgnd;
2829 
2830  TAllRoutes::TRouteType RouteType;
2831 
2832  RouteType = AllRoutes->GetRouteTypeAndGraphics(11, Element, EntryPos, EXGraphicPtr, EntryDirectionGraphicPtr);
2833 
2834  TRect SourceRect, DestRect;
2835 
2836  DestRect.init(0, 0, 8, 8); // initialise left, top, right, bottom
2837  // note right and bottom rect co-ordinates are 1 greater than the pixel area
2838  SourceRect.init(HOffset, VOffset, HOffset + 8, VOffset + 8);
2839  Graphics::TBitmap *TempGraphic = new Graphics::TBitmap;
2840 
2841  TempGraphic->PixelFormat = pf8bit;
2842  TempGraphic->Width = 16;
2843  TempGraphic->Height = 16;
2844  TTrackElement TempElement = Track->TrackElementAt(286, Element);
2845 
2846  if(TempElement.TrackType == Points)
2847  {
2848  TempGraphic->Assign(TempElement.GraphicPtr);
2849  TempGraphic->Transparent = true;
2850  TempGraphic->TransparentColor = Utilities->clTransparent;
2851  if(RouteType == TAllRoutes::AutoSigsRoute)
2852  {
2853  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
2854  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
2855  }
2856  else
2857  {
2858  TempGraphic->Canvas->Draw(0, 0, Track->GetFilletGraphic(1, TempElement)); // add fillet
2859  }
2860  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
2861  }
2862  else if(TempElement.TrackType == GapJump) // plot set gap
2863  {
2864  if(TempElement.SpeedTag == 88)
2865  {
2866  TempGraphic->Assign(RailGraphics->gl88set);
2867  }
2868  else if(TempElement.SpeedTag == 89)
2869  {
2870  TempGraphic->Assign(RailGraphics->gl89set);
2871  }
2872  else if(TempElement.SpeedTag == 90)
2873  {
2874  TempGraphic->Assign(RailGraphics->gl90set);
2875  }
2876  else if(TempElement.SpeedTag == 91)
2877  {
2878  TempGraphic->Assign(RailGraphics->gl91set);
2879  }
2880  else if(TempElement.SpeedTag == 92)
2881  {
2882  TempGraphic->Assign(RailGraphics->gl92set);
2883  }
2884  else if(TempElement.SpeedTag == 93)
2885  {
2886  TempGraphic->Assign(RailGraphics->bm93set);
2887  }
2888  else if(TempElement.SpeedTag == 94)
2889  {
2890  TempGraphic->Assign(RailGraphics->bm94set);
2891  }
2892  else if(TempElement.SpeedTag == 95)
2893  {
2894  TempGraphic->Assign(RailGraphics->gl95set);
2895  }
2896  TempGraphic->Transparent = true;
2897  TempGraphic->TransparentColor = Utilities->clTransparent;
2898  if(RouteType == TAllRoutes::AutoSigsRoute)
2899  {
2900  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
2901  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
2902  }
2903  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
2904  }
2905  // new for version 0.6
2906  else if(TempElement.TrackType == SignalPost)
2907  {
2908  if(TempElement.SigAspect == TTrackElement::GroundSignal)
2909  {
2910  for(int x = 0; x < 40; x++)
2911  {
2912  if((Track->SigTableGroundSignal[x].SpeedTag == TempElement.SpeedTag) && (Track->SigTableGroundSignal[x].Attribute == 0))
2913  // need to stop aspect
2914  {
2915  TempGraphic->Assign(Track->SigTableGroundSignal[x].SigPtr);
2916  break;
2917  }
2918  }
2919  }
2920  else // normal signal
2921  {
2922  TempGraphic->Assign(TempElement.GraphicPtr);
2923  // GraphicPtr set to normal signal in a signal track element
2924  }
2925  TempGraphic->Transparent = true;
2926  TempGraphic->TransparentColor = Utilities->clTransparent;
2927  if(RouteType == TAllRoutes::AutoSigsRoute)
2928  {
2929  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
2930  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
2931  }
2932  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
2933  }
2934  else
2935  {
2936  // first check if there's a NamedNonStationLocation element at that position & if so pick up that as the background
2937  // can't name points gaps or signals so 'else' OK
2938  bool FoundFlag;
2939  TTrack::TIMPair IMPair = Track->GetVectorPositionsFromInactiveTrackMap(4, TempElement.HLoc, TempElement.VLoc, FoundFlag);
2940  if(FoundFlag)
2941  {
2943  {
2944  GraphicPtr->Canvas->CopyRect(DestRect, Track->InactiveTrackElementAt(26, IMPair.first).GraphicPtr->Canvas, SourceRect);
2945  TempGraphic->Assign(RailGraphics->bmName);
2946  TempGraphic->Transparent = true;
2947  TempGraphic->TransparentColor = Utilities->clTransparent;
2948  if(RouteType == TAllRoutes::AutoSigsRoute)
2949  {
2950  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
2951  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
2952  }
2953  else
2954  {
2955  TempGraphic->Canvas->Draw(0, 0, TempElement.GraphicPtr);
2956  }
2957  // draw track on top
2958  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
2959  }
2960  else if(Track->InactiveTrackElementAt(116, IMPair.first).TrackType == LevelCrossing)
2961  {
2962  TempGraphic->Assign(TempElement.GraphicPtr);
2963  TempGraphic->Transparent = true;
2964  TempGraphic->TransparentColor = Utilities->clTransparent;
2965  // note that can't be an AutoSigsRoute
2966  // now overlay the LC central portion
2967  int BDVectorPos = -1; //not used
2968  if(Track->AnyLinkedBarrierDownVectorManual(0, Track->InactiveTrackElementAt(130, IMPair.first).HLoc, Track->InactiveTrackElementAt(131, IMPair.first).VLoc, BDVectorPos))
2969  {
2970  TempGraphic->Canvas->Draw(0, 0, RailGraphics->LCPlainMan);
2971  }
2972  else
2973  {
2974  TempGraphic->Canvas->Draw(0, 0, RailGraphics->LCPlain);
2975  }
2976  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
2977  }
2978  else
2979  {
2980  TempGraphic->Assign(TempElement.GraphicPtr);
2981  TempGraphic->Transparent = true;
2982  TempGraphic->TransparentColor = Utilities->clTransparent;
2983  if(RouteType == TAllRoutes::AutoSigsRoute)
2984  {
2985  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
2986  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
2987  }
2988  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
2989  }
2990  }
2991  else
2992  {
2993  TempGraphic->Assign(TempElement.GraphicPtr);
2994  TempGraphic->Transparent = true;
2995  TempGraphic->TransparentColor = Utilities->clTransparent;
2996  if(RouteType == TAllRoutes::AutoSigsRoute)
2997  {
2998  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
2999  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3000  }
3001  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3002  }
3003  }
3004  delete TempGraphic;
3005  Utilities->CallLogPop(675);
3006 }
3007 
3008 // ---------------------------------------------------------------------------
3009 
3010 // This was an attempt to pick up the actual 8x8 graphic from the display, so that text & user graphics would show as soon as the train passed, and overwrite it with the
3011 // reconstructed track, and it works ok but for the little arrows showing route directions at start and end, which extend beyond the track. It doesn't matter for autosig
3012 // routes because they are replotted (alomg with the direction arrows) but for others they shouldn't be. Leave in in case an easy way to remove these pointers comes to mind.
3013 /*
3014 
3015  void TTrain::PickUpBackgroundBitmap(int Caller, int HOffset, int VOffset, int Element, int EntryPos, Graphics::TBitmap *GraphicPtr) const
3016  {
3017  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PickUpBackgroundBitmap," + AnsiString(HOffset) +
3018  "," + AnsiString(VOffset) + "," + AnsiString(Element) + "," + AnsiString(EntryPos) + "," + HeadCode);
3019  TAllRoutes::TRouteType RouteType;
3020  Graphics::TBitmap *EXGraphicPtr = RailGraphics->bmTransparentBgnd;
3021  // default values
3022  Graphics::TBitmap *EntryDirectionGraphicPtr = RailGraphics->bmTransparentBgnd;
3023  TTrackElement TempElement = Track->TrackElementAt(, Element); //this is a copy of the element passed into the function
3024  TRect SourceRect, DestRect, ScreenSourceRect;
3025  RouteType = AllRoutes->GetRouteTypeAndGraphics(7, Element, EntryPos, EXGraphicPtr, EntryDirectionGraphicPtr);
3026 
3027  DestRect.init(0, 0, 8, 8); //initialise left, top, right, bottom
3028  // note right and bottom rect co-ordinates are 1 greater than the pixel area
3029  SourceRect.init(HOffset, VOffset, HOffset + 8, VOffset + 8);
3030 
3031  //add text & user graphics if any to *GraphicPtr prior to adding the track
3032  int Left = ((TempElement.HLoc - Display->DisplayOffsetH) * 16) + HOffset;
3033  int Top = ((TempElement.VLoc - Display->DisplayOffsetV) * 16) + VOffset;
3034  int Right = Left + 8;
3035  int Bottom = Top + 8;
3036  ScreenSourceRect.init(Left, Top, Right, Bottom);
3037  GraphicPtr->Canvas->CopyMode = cmSrcCopy;
3038  GraphicPtr->Canvas->CopyRect(DestRect, Display->GetImage()->Canvas, ScreenSourceRect);
3039 
3040  Graphics::TBitmap *TempGraphic = new Graphics::TBitmap; //this will hold the 16x16 reconstructed element, prior to transfer of the 8x8 bit to *GraphicPtr
3041  TempGraphic->PixelFormat = pf8bit;
3042  TempGraphic->Width = 16;
3043  TempGraphic->Height = 16;
3044 
3045  Graphics::TBitmap *SourceGraphic = new Graphics::TBitmap; //this will hold the 8x8 element segment from TempGraphic - needed because to keep transparency have to use Draw, not CopyRect
3046  SourceGraphic->PixelFormat = pf8bit;
3047  SourceGraphic->Width = 16;
3048  SourceGraphic->Height = 16;
3049  SourceGraphic->Transparent = true;
3050  SourceGraphic->TransparentColor = Utilities->clTransparent;
3051 
3052  if (TempElement.TrackType == Points)
3053  {
3054  TempGraphic->Assign(TempElement.GraphicPtr);
3055  TempGraphic->Transparent = true;
3056  TempGraphic->TransparentColor = Utilities->clTransparent;
3057  if (RouteType == TAllRoutes::AutoSigsRoute)
3058  {
3059  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3060  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3061  }
3062  else
3063  {
3064  TempGraphic->Canvas->Draw(0, 0, Track->GetFilletGraphic(1, TempElement)); // add fillet
3065  }
3066  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3067  }
3068  else if (TempElement.TrackType == GapJump) // plot set gap
3069  {
3070  if (TempElement.SpeedTag == 88)
3071  TempGraphic->Assign(RailGraphics->gl88set);
3072  else if (TempElement.SpeedTag == 89)
3073  TempGraphic->Assign(RailGraphics->gl89set);
3074  else if (TempElement.SpeedTag == 90)
3075  TempGraphic->Assign(RailGraphics->gl90set);
3076  else if (TempElement.SpeedTag == 91)
3077  TempGraphic->Assign(RailGraphics->gl91set);
3078  else if (TempElement.SpeedTag == 92)
3079  TempGraphic->Assign(RailGraphics->gl92set);
3080  else if (TempElement.SpeedTag == 93)
3081  TempGraphic->Assign(RailGraphics->bm93set);
3082  else if (TempElement.SpeedTag == 94)
3083  TempGraphic->Assign(RailGraphics->bm94set);
3084  else if (TempElement.SpeedTag == 95)
3085  TempGraphic->Assign(RailGraphics->gl95set);
3086  TempGraphic->Transparent = true;
3087  TempGraphic->TransparentColor = Utilities->clTransparent;
3088  if (RouteType == TAllRoutes::AutoSigsRoute) {
3089  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3090  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3091  }
3092  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3093  }
3094  // new for version 0.6
3095  else if (TempElement.TrackType == SignalPost)
3096  {
3097  if (TempElement.SigAspect == TTrackElement::GroundSignal)
3098  {
3099  for (int x = 0; x < 40; x++)
3100  {
3101  if ((Track->SigTableGroundSignal[x].SpeedTag == TempElement.SpeedTag) && (Track->SigTableGroundSignal[x].Attribute == 0))
3102  // need to stop aspect
3103  {
3104  TempGraphic->Assign(Track->SigTableGroundSignal[x].SigPtr);
3105  break;
3106  }
3107  }
3108  }
3109  else // normal signal
3110  {
3111  TempGraphic->Assign(TempElement.GraphicPtr);
3112  // GraphicPtr set to normal signal in a signal track element
3113  }
3114  TempGraphic->Transparent = true;
3115  TempGraphic->TransparentColor = Utilities->clTransparent;
3116  if (RouteType == TAllRoutes::AutoSigsRoute) {
3117  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3118  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3119  }
3120  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3121  }
3122  else {
3123  // first check if there's a NamedNonStationLocation element at that position & if so pick up that as the background
3124  // can't name points gaps or signals so 'else' OK
3125  bool FoundFlag;
3126  TTrack::TIMPair IMPair = Track->GetVectorPositionsFromInactiveTrackMap
3127  (4, TempElement.HLoc, TempElement.VLoc, FoundFlag);
3128  if (FoundFlag)
3129  {
3130  if (Track->InactiveTrackElementAt(, IMPair.first).TrackType == NamedNonStationLocation)
3131  {
3132  GraphicPtr->Canvas->CopyRect(DestRect,
3133  Track->InactiveTrackElementAt(, IMPair.first).GraphicPtr->Canvas, SourceRect);
3134  TempGraphic->Assign(RailGraphics->bmName);
3135  TempGraphic->Transparent = true;
3136  TempGraphic->TransparentColor = Utilities->clTransparent;
3137  if (RouteType == TAllRoutes::AutoSigsRoute)
3138  {
3139  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3140  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3141  }
3142  else
3143  TempGraphic->Canvas->Draw(0, 0, TempElement.GraphicPtr);
3144  // draw track on top
3145  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas,
3146  SourceRect);
3147  }
3148  else if (Track->InactiveTrackElementAt(, IMPair.first).TrackType == LevelCrossing) {
3149  TempGraphic->Assign(TempElement.GraphicPtr);
3150  TempGraphic->Transparent = true;
3151  TempGraphic->TransparentColor = Utilities->clTransparent;
3152  // note that can't be an AutoSigsRoute
3153  // now overlay the LC central portion
3154  TempGraphic->Canvas->Draw(0, 0, RailGraphics->LCPlain);
3155  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas,
3156  SourceRect);
3157  }
3158  else {
3159  TempGraphic->Assign(TempElement.GraphicPtr);
3160  TempGraphic->Transparent = true;
3161  TempGraphic->TransparentColor = Utilities->clTransparent;
3162  if (RouteType == TAllRoutes::AutoSigsRoute) {
3163  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3164  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3165  }
3166  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas,
3167  SourceRect);
3168  }
3169  }
3170  else {
3171  TempGraphic->Assign(TempElement.GraphicPtr);
3172  TempGraphic->Transparent = true;
3173  TempGraphic->TransparentColor = Utilities->clTransparent;
3174  if (RouteType == TAllRoutes::AutoSigsRoute) {
3175  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3176  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3177  }
3178  SourceGraphic->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3179  GraphicPtr->Canvas->Draw(0, 0, SourceGraphic);
3180  }
3181  }
3182  delete TempGraphic;
3183  delete SourceGraphic;
3184  Utilities->CallLogPop();
3185  }
3186 */
3187 // ---------------------------------------------------------------------------
3188 
3189 void TTrain::PlotTrainGraphic(int Caller, int ArrayNumber, TDisplay *Disp)
3190 {
3191  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrainGraphic," + AnsiString(ArrayNumber) + "," + HeadCode);
3192  if(PlotElement[ArrayNumber] == -1)
3193  {
3194  Utilities->CallLogPop(676);
3195  return; // not plotted yet
3196  }
3197  SetTrainElementID(0, PlotElement[ArrayNumber], PlotEntryPos[ArrayNumber]);
3198  // set before plot so gap flashing stops first
3199  Disp->PlotOutput(29, ((Track->TrackElementAt(295, PlotElement[ArrayNumber]).HLoc * 16) + HOffset[ArrayNumber]),
3200  ((Track->TrackElementAt(296, PlotElement[ArrayNumber]).VLoc * 16) + VOffset[ArrayNumber]), HeadCodePosition[ArrayNumber]);
3201  // Only need to set ID for leading element, stays set until train finally leaves the element
3202  Plotted = true;
3203  Utilities->CallLogPop(677);
3204 }
3205 
3206 // ---------------------------------------------------------------------------
3207 
3208 void TTrain::PlotBackgroundGraphic(int Caller, int ArrayNumber, TDisplay *Disp) const
3209 {
3210  Disp->PlotOutput(30, ((Track->TrackElementAt(297, PlotElement[ArrayNumber]).HLoc * 16) + HOffset[ArrayNumber]),
3211  ((Track->TrackElementAt(298, PlotElement[ArrayNumber]).VLoc * 16) + VOffset[ArrayNumber]), BackgroundPtr[ArrayNumber]);
3212 }
3213 
3214 // ---------------------------------------------------------------------------
3215 
3216 bool TTrain::BufferAtExit(int Caller, int Element, int ExitPos) const
3217 {
3218  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",BufferAtExit," + AnsiString(Element) + "," + AnsiString(ExitPos) + "," +
3219  HeadCode);
3220  if((Track->TrackElementAt(299, Element).TrackType == Buffers) && (Track->TrackElementAt(300, Element).Config[ExitPos] == End))
3221  {
3222  Utilities->CallLogPop(678);
3223  return(true);
3224  }
3225  else
3226  {
3227  Utilities->CallLogPop(679);
3228  return(false);
3229  }
3230 }
3231 
3232 // ---------------------------------------------------------------------------
3233 
3234 bool TTrain::ContinuationExit(int Caller, int Element, int ExitPos) const
3235 {
3236  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ContinuationExit," + AnsiString(Element) + "," + AnsiString(ExitPos) +
3237  "," + HeadCode);
3238  if((Track->TrackElementAt(301, Element).TrackType == Continuation) && (Track->TrackElementAt(302, Element).Config[ExitPos] == End))
3239  {
3240  Utilities->CallLogPop(680);
3241  return(true);
3242  }
3243  else
3244  {
3245  Utilities->CallLogPop(681);
3246  return(false);
3247  }
3248 }
3249 
3250 // ---------------------------------------------------------------------------
3251 
3252 bool TTrain::IsTrainIDOnBridgeTrackPos01(int Caller, unsigned int TrackVectorPosition)
3253 // test whether this train on a bridge on trackpos 0 & 1
3254 {
3255  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsTrainIDOnBridgeTrackPos01," + AnsiString(TrackVectorPosition) + "," +
3256  HeadCode);
3257  if(Track->TrackElementAt(303, TrackVectorPosition).TrackType != Bridge)
3258  {
3259  Utilities->CallLogPop(682);
3260  return(false);
3261  }
3262  // if(Track->TrackElementAt(304, TrackVectorPosition).TrainIDOnElement != TrainID) return false; No, if a bridge could be one of 2 TrainIDs
3264  {
3266  {
3267  throw Exception("Error, same train on two different bridge tracks");
3268  }
3269  else
3270  {
3271  Utilities->CallLogPop(684);
3272  return(true);
3273  }
3274  }
3275  else
3276  {
3277  Utilities->CallLogPop(685);
3278  return(false);
3279  }
3280 }
3281 
3282 // ---------------------------------------------------------------------------
3283 
3284 bool TTrain::IsTrainIDOnBridgeTrackPos23(int Caller, unsigned int TrackVectorPosition)
3285 // test whether this train on a bridge on trackpos 2 & 3
3286 {
3287  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsTrainIDOnBridgeTrackPos23," + AnsiString(TrackVectorPosition) + "," +
3288  HeadCode);
3289  if(Track->TrackElementAt(307, TrackVectorPosition).TrackType != Bridge)
3290  {
3291  Utilities->CallLogPop(686);
3292  return(false);
3293  }
3294  // if(Track->TrackElementAt(308, TrackVectorPosition).TrainIDOnElement != TrainID) return false; No, if a bridge could be one of 2 TrainIDs
3296  {
3297  // don't carry out check for train on tracks 0 & 1 else will enter an infinite loop if train on both
3298  Utilities->CallLogPop(687);
3299  return(true);
3300  }
3301  else
3302  {
3303  Utilities->CallLogPop(688);
3304  return(false);
3305  }
3306 }
3307 
3308 // ---------------------------------------------------------------------------
3309 
3310 void TTrain::SetTrainElementID(int Caller, unsigned int TrackVectorPosition, int EntryPos)
3311 {
3312  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SetTrainElementID," + AnsiString(TrackVectorPosition) + "," +
3313  AnsiString(EntryPos) + "," + HeadCode);
3314  Track->TrackElementAt(310, TrackVectorPosition).TrainIDOnElement = TrainID;
3315 
3316  // unplot GapFlash graphics if land on flashing gap (this done before train plotted - see PlotTrainGraphic)
3317  if(Track->GapFlashFlag)
3318  {
3320  {
3323  Track->GapFlashFlag = false;
3324  }
3325  }
3326  if(Track->TrackElementAt(311, TrackVectorPosition).TrackType == Bridge)
3327  {
3328  if(EntryPos == -1)
3329  {
3330  throw Exception("Error, TrackVectorPosition set but not EntryPos in SetTrainElementID");
3331  }
3332  if(EntryPos < 2)
3333  {
3335  }
3336  else
3337  {
3339  }
3340  }
3341  Utilities->CallLogPop(690);
3342 }
3343 
3344 // ---------------------------------------------------------------------------
3345 
3346 void TTrain::ResetTrainElementID(int Caller, unsigned int TrackVectorPosition, int EntryPos)
3347 {
3348  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ResetTrainElementID," + AnsiString(TrackVectorPosition) + "," +
3349  AnsiString(EntryPos) + "," + HeadCode);
3350  if(Track->TrackElementAt(314, TrackVectorPosition).TrackType != Bridge)
3351  {
3352  Track->TrackElementAt(315, TrackVectorPosition).TrainIDOnElement = -1;
3353  }
3354  else
3355  {
3356  if(EntryPos == -1)
3357  {
3358  throw Exception("Error, TrackVectorPosition set but not EntryPos in ResetTrainElementID");
3359  }
3360  if(EntryPos < 2)
3361  {
3362  Track->TrackElementAt(316, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 = -1;
3363  }
3364  else
3365  {
3366  Track->TrackElementAt(317, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 = -1;
3367  }
3368  if((EntryPos < 2) && (Track->TrackElementAt(318, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 > -1))
3369  // i.e. other train on track 2&3
3370  {
3371  Track->TrackElementAt(319, TrackVectorPosition).TrainIDOnElement = Track->TrackElementAt(320, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit23;
3372  }
3373  else if((EntryPos > 1) && (Track->TrackElementAt(321, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 > -1))
3374  // i.e. other train on track 1&2
3375  {
3376  Track->TrackElementAt(322, TrackVectorPosition).TrainIDOnElement = Track->TrackElementAt(323, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit01;
3377  }
3378  else
3379  {
3380  Track->TrackElementAt(324, TrackVectorPosition).TrainIDOnElement = -1;
3381  }
3382  }
3383  Utilities->CallLogPop(691);
3384 }
3385 
3386 // ---------------------------------------------------------------------------
3387 
3388 void TTrain::PlotAlternativeTrackRouteGraphic(int Caller, unsigned int ElementVecNum, int ElementEntryPos, int HOffset, int VOffset, TStraddle StraddleValue)
3389 {
3390  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotAlternativeTrackRouteGraphic," + AnsiString(ElementVecNum) + "," +
3391  AnsiString(ElementEntryPos) + "," + AnsiString(HOffset) + "," + AnsiString(VOffset) + "," + AnsiString(StraddleValue) + "," + HeadCode);
3392  int LockedVectorNumber;
3393 
3394  if(Track->TrackElementAt(325, ElementVecNum).TrackType != Bridge)
3395  // && (Track->TrackElementAt(326, ElementVecNum).TrackType != Crossover))
3396  {
3397  // only applies for a bridge as there can't be (or shouldn't be) 2 routes on an element that isn't a bridge
3398  Utilities->CallLogPop(692);
3399  return;
3400  }
3401  if(AllRoutes->TrackIsInARoute(0, ElementVecNum, (3 - ElementEntryPos)))
3402  // i.e other track is in a marked route
3403  // LinkPos doesn't have to be the entry position for the above check
3404  {
3405  TRect SourceRect, DestRect;
3406  DestRect.init(0, 0, 8, 8); // left, top, right, bottom
3407  // note right and bottom rect co-ordinates are 1 greater than the pixel area
3408  SourceRect.init(HOffset, VOffset, HOffset + 8, VOffset + 8);
3409  // identify the route element for the other track
3410  TAllRoutes::TRouteElementPair RoutePair1, RoutePair2;
3411  RoutePair1 = AllRoutes->GetRouteElementDataFromRoute2MultiMap(13, Track->TrackElementAt(327, ElementVecNum).HLoc,
3412  Track->TrackElementAt(328, ElementVecNum).VLoc, RoutePair2);
3413  int FirstELink, SecondELink = -1;
3414  FirstELink = AllRoutes->GetFixedRouteAt(149, RoutePair1.first).GetFixedPrefDirElementAt(159, RoutePair1.second).GetELink();
3415  // must be at least one
3416  if(RoutePair2.first > -1)
3417  {
3418  SecondELink = AllRoutes->GetFixedRouteAt(150, RoutePair2.first).GetFixedPrefDirElementAt(160, RoutePair2.second).GetELink();
3419  }
3420  TPrefDirElement RouteElement;
3421  // Graphics::TBitmap *RouteGraphic;
3422  if(FirstELink == Track->TrackElementAt(329, ElementVecNum).Link[ElementEntryPos])
3423  // i.e. other track is in RoutePair2
3424  {
3425  if(SecondELink == -1)
3426  {
3427  throw Exception("Error - Second ELink should be set but isn't in PlotAlternativeTrackRouteGraphic [1]");
3428  }
3429  if(SecondELink == Track->TrackElementAt(330, ElementVecNum).Link[ElementEntryPos])
3430  // error if both have same Link number
3431  {
3432  throw Exception("Error - First & Second ELinks have same value in PlotAlternativeTrackRouteGraphic");
3433  }
3434  // RouteGraphic = AllRoutes->GetFixedRouteAt(151, RoutePair2.first).GetFixedPrefDirElementAt(161, RoutePair2.second).GetEXGraphicPtr();
3435  RouteElement = AllRoutes->GetFixedRouteAt(152, RoutePair2.first).GetFixedPrefDirElementAt(162, RoutePair2.second);
3436  }
3437  else // other track is in RoutePair1
3438  {
3439  // RouteGraphic = AllRoutes->GetFixedRouteAt(153, RoutePair1.first).GetFixedPrefDirElementAt(163, RoutePair1.second).GetEXGraphicPtr();
3440  RouteElement = AllRoutes->GetFixedRouteAt(154, RoutePair1.first).GetFixedPrefDirElementAt(164, RoutePair1.second);
3441  }
3442  Graphics::TBitmap *DestGraphic = new Graphics::TBitmap;
3443  DestGraphic->PixelFormat = pf8bit;
3444  DestGraphic->Width = 8;
3445  DestGraphic->Height = 8;
3446  DestGraphic->Transparent = true;
3447  // has to be transparent or will overwrite the track that the train has just left
3448  DestGraphic->TransparentColor = Utilities->clTransparent;
3449  DestGraphic->Canvas->CopyRect(DestRect, RouteElement.GetRouteEXGraphicPtr()->Canvas, SourceRect);
3450  Display->PlotOutput(31, (Track->TrackElementAt(331, ElementVecNum).HLoc * 16) + HOffset,
3451  (Track->TrackElementAt(332, ElementVecNum).VLoc * 16) + VOffset, DestGraphic);
3452  // plot locked route marker for other route if appropriate
3453  TPrefDirElement PrefDirElement; // holder for next call, unused
3454  // plot locked route marker if appropriate, but only when train leaves element completely as this is a 16x16 graphic
3455  if(StraddleValue == LeadMidLag)
3456  {
3458  PrefDirElement, LockedVectorNumber))
3459  {
3460  Display->PlotOutput(32, (Track->TrackElementAt(333, ElementVecNum).HLoc * 16), (Track->TrackElementAt(334, ElementVecNum).VLoc * 16),
3461  RailGraphics->LockedRouteCancelPtr[RouteElement.GetELink()]);
3462  }
3463  }
3464  delete DestGraphic;
3465  }
3466  // but - there may be a train on the other track - if so need to replot it else the section of route overwrites it
3467  // also can only be a bridge or trains either have already or soon will crash
3468  if(Track->TrackElementAt(335, ElementVecNum).TrackType != Bridge)
3469  {
3470  Utilities->CallLogPop(695);
3471  return;
3472  }
3473  if(ElementEntryPos > 1) // other train is on track 01
3474  {
3476  {
3478  }
3479  }
3480  else // other train is on track 23
3481  {
3483  {
3485  }
3486  }
3487  Utilities->CallLogPop(696);
3488 }
3489 
3490 // ---------------------------------------------------------------------------
3491 
3492 void TTrain::CheckAndCancelRouteForWrongEndEntry(int Caller, int Element, int EntryPos)
3493 {
3494  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckAndCancelRouteForWrongEndEntry," + AnsiString(Element) + "," +
3495  AnsiString(EntryPos) + "," + HeadCode);
3496  int RouteNumber;
3497  bool WrongRoute = false;
3498  TPrefDirElement RouteElement;
3500  TAllRoutes::TRoute2MultiMapIterator Route2MultiMapIterator;
3501 
3502  if(AllRoutes->GetRouteTypeAndNumber(11, Element, EntryPos, RouteNumber) == TAllRoutes::NoRoute)
3503  // here if single track element & no route, or double track element with no route at EntryPos but still need to check if on points or a crossover on non-route track,
3504  // and force-erase route if so (bridge OK of course) note that GetRouteTypeAndNumber allows for points having an EntryPos of 0 or 2 & still returns correct values
3505  {
3506  if((Track->TrackElementAt(340, Element).TrackType == Crossover) || (Track->TrackElementAt(341, Element).TrackType == Points))
3507  {
3508  if(AllRoutes->GetRouteTypeAndNumber(12, Element, (3 - EntryPos), RouteNumber) != TAllRoutes::NoRoute)
3509  // (3-EntryPos) guarantees other route (0->3; 1->2; 2->1; 3->0)
3510  {
3511  if(AllRoutes->GetFixedRouteAt(179, RouteNumber).PrefDirSize() > 2)
3512  {
3513  // don't call for stub end routes
3515  }
3516  AllRoutes->GetModifiableRouteAt(13, RouteNumber).ForceCancelRoute(1);
3517  Utilities->CallLogPop(697);
3518  return;
3519  }
3520  }
3521  // also need to check for a route on a crossing diagonal
3522  TTrackElement TrackElement = Track->TrackElementAt(892, Element);
3523  int LinkNumber = TrackElement.Link[EntryPos];
3524  if((LinkNumber == 1) || (LinkNumber == 3) || (LinkNumber == 7) || (LinkNumber == 9))
3525  {
3526  if(AllRoutes->DiagonalFouledByRoute(0, TrackElement.HLoc, TrackElement.VLoc, LinkNumber))
3527  {
3528  // for LinkNumber = 1, potentially fouled diagonals are at H-1, V, Lk 3 & H, V-1, Lk 7
3529  bool LogActionErrorCalled = false;
3530  // to ensure only called once if have 2 routes on the 2 crossed diagonals
3531  if(LinkNumber == 1)
3532  {
3533  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(0, TrackElement.HLoc - 1, TrackElement.VLoc, 3, RouteNumber))
3534  {
3535  if(AllRoutes->GetFixedRouteAt(207, RouteNumber).PrefDirSize() > 2)
3536  {
3537  // don't call for stub end routes
3539  LogActionErrorCalled = true;
3540  }
3541  AllRoutes->GetModifiableRouteAt(20, RouteNumber).ForceCancelRoute(3);
3542  }
3543  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(1, TrackElement.HLoc, TrackElement.VLoc - 1, 7, RouteNumber))
3544  // not else in case have different routes on each diagonal, though shouldn't be possible
3545  {
3546  if(!LogActionErrorCalled && AllRoutes->GetFixedRouteAt(208, RouteNumber).PrefDirSize() > 2)
3547  {
3548  // don't call for stub end routes
3550  }
3551  AllRoutes->GetModifiableRouteAt(21, RouteNumber).ForceCancelRoute(4);
3552  }
3553  }
3554 
3555  // for LinkNumber = 3, potentially fouled diagonals are at H+1, V, Lk 1 & H, V-1 Lk 9
3556  else if(LinkNumber == 3)
3557  {
3558  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(2, TrackElement.HLoc + 1, TrackElement.VLoc, 1, RouteNumber))
3559  {
3560  if(AllRoutes->GetFixedRouteAt(209, RouteNumber).PrefDirSize() > 2)
3561  {
3562  // don't call for stub end routes
3564  LogActionErrorCalled = true;
3565  }
3566  AllRoutes->GetModifiableRouteAt(22, RouteNumber).ForceCancelRoute(5);
3567  }
3568  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(3, TrackElement.HLoc, TrackElement.VLoc - 1, 9, RouteNumber))
3569  // not else in case have different routes on each diagonal, though shouldn't be possible
3570  {
3571  if(!LogActionErrorCalled && AllRoutes->GetFixedRouteAt(210, RouteNumber).PrefDirSize() > 2)
3572  {
3573  // don't call for stub end routes
3575  }
3576  AllRoutes->GetModifiableRouteAt(23, RouteNumber).ForceCancelRoute(6);
3577  }
3578  }
3579 
3580  // for LinkNumber = 7, potentially fouled diagonals are at H-1, V, Lk 9 & H, V+1 Lk 1
3581  else if(LinkNumber == 7)
3582  {
3583  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(4, TrackElement.HLoc - 1, TrackElement.VLoc, 9, RouteNumber))
3584  {
3585  if(AllRoutes->GetFixedRouteAt(211, RouteNumber).PrefDirSize() > 2)
3586  {
3587  // don't call for stub end routes
3589  LogActionErrorCalled = true;
3590  }
3591  AllRoutes->GetModifiableRouteAt(24, RouteNumber).ForceCancelRoute(7);
3592  }
3593  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(5, TrackElement.HLoc, TrackElement.VLoc + 1, 1, RouteNumber))
3594  // not else in case have different routes on each diagonal, though shouldn't be possible
3595  {
3596  if(!LogActionErrorCalled && AllRoutes->GetFixedRouteAt(212, RouteNumber).PrefDirSize() > 2)
3597  {
3598  // don't call for stub end routes
3600  }
3601  AllRoutes->GetModifiableRouteAt(25, RouteNumber).ForceCancelRoute(8);
3602  }
3603  }
3604 
3605  // for LinkNumber = 9, potentially fouled diagonals are at H+1, V, Lk 7 & H, V+1 Lk 3
3606  else if(LinkNumber == 9)
3607  {
3608  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(6, TrackElement.HLoc + 1, TrackElement.VLoc, 7, RouteNumber))
3609  {
3610  if(AllRoutes->GetFixedRouteAt(213, RouteNumber).PrefDirSize() > 2)
3611  {
3612  // don't call for stub end routes
3614  LogActionErrorCalled = true;
3615  }
3616  AllRoutes->GetModifiableRouteAt(26, RouteNumber).ForceCancelRoute(9);
3617  }
3618  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(7, TrackElement.HLoc, TrackElement.VLoc + 1, 3, RouteNumber))
3619  // not else in case have different routes on each diagonal, though shouldn't be possible
3620  {
3621  if(!LogActionErrorCalled && AllRoutes->GetFixedRouteAt(214, RouteNumber).PrefDirSize() > 2)
3622  {
3623  // don't call for stub end routes
3625  }
3626  AllRoutes->GetModifiableRouteAt(27, RouteNumber).ForceCancelRoute(10);
3627  }
3628  }
3629  }
3630  }
3631  Utilities->CallLogPop(698);
3632  return; // no route on other track or no other track
3633  }
3634  // here if there is a route at Element & EntryPos - so there can't be a route on the other track if a 4 track element, unless it's a bridge & that's ok
3635  for(unsigned int x = 0; x < AllRoutes->GetFixedRouteAt(155, RouteNumber).PrefDirSize(); x++)
3636  {
3637  RouteElement = AllRoutes->GetFixedRouteAt(156, RouteNumber).GetFixedPrefDirElementAt(165, x);
3638  bool PointsAtElement = (Track->TrackElementAt(987, Element).TrackType == Points); // new at v2.4.2 for points check - Xeon repoted it 30/05/20. He found that for routes that
3639  if(RouteElement.GetTrackVectorPosition() == (unsigned int)Element) // cross bridges at both levels can have entrypos 0 & other exitpos 2 so if don't have this check can cancel a route wrongly
3640  {
3641  if(RouteElement.GetELinkPos() == EntryPos)
3642  {
3643  Utilities->CallLogPop(699);
3644  return; // right direction
3645  }
3646  else if((RouteElement.GetELinkPos() == 2) && (EntryPos == 0) && PointsAtElement)
3647  {
3648  Utilities->CallLogPop(700);
3649  return; // right direction (points)
3650  }
3651  else if((RouteElement.GetELinkPos() == 0) && (EntryPos == 2) && PointsAtElement)
3652  {
3653  Utilities->CallLogPop(701);
3654  return; // right direction (points)
3655  }
3656  else if(RouteElement.GetXLinkPos() == EntryPos)
3657  {
3658  WrongRoute = true;
3659  break; // wrong direction
3660  }
3661  else if((RouteElement.GetXLinkPos() == 2) && (EntryPos == 0) && PointsAtElement) // ok for bridges
3662  {
3663  WrongRoute = true;
3664  break; // wrong direction
3665  }
3666  else if((RouteElement.GetXLinkPos() == 0) && (EntryPos == 2) && PointsAtElement) // ok for bridges
3667  {
3668  WrongRoute = true;
3669  break; // wrong direction
3670  }
3671  }
3672  }
3673  if(!WrongRoute)
3674  {
3675  throw Exception("Error, Element in route but no route found in CheckAndCancelRouteForWrongEndEntry");
3676  }
3677  if(AllRoutes->GetFixedRouteAt(180, RouteNumber).PrefDirSize() > 2)
3678  {
3679  // don't call for stub end routes
3681  }
3682  AllRoutes->GetModifiableRouteAt(14, RouteNumber).ForceCancelRoute(2);
3683  Utilities->CallLogPop(703);
3684 }
3685 
3686 // ---------------------------------------------------------------------------
3687 
3688 void TTrain::PlotTrainWithNewBackgroundColour(int Caller, TColor NewBackgroundColour, TDisplay *Disp)
3689 {
3690  if(BackgroundColour == NewBackgroundColour)
3691  {
3692  return; // don't replot if already correct
3693 
3694  }
3695  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrainWithNewBackgroundColour," + AnsiString(NewBackgroundColour));
3696  bool ColourError = false, ColourError2 = false;
3697 
3698  RailGraphics->ChangeBackgroundColour(1, FrontCodePtr, FrontCodePtr, NewBackgroundColour, BackgroundColour, ColourError);
3699  if(ColourError)
3700  {
3701  ColourError2 = true;
3702  }
3703  for(int x = 0; x < 4; x++)
3704  {
3705  RailGraphics->ChangeBackgroundColour(2, HeadCodeGrPtr[x], HeadCodeGrPtr[x], NewBackgroundColour, BackgroundColour, ColourError);
3706  if(ColourError)
3707  {
3708  ColourError2 = true;
3709  }
3710  }
3711  if(ColourError2)
3712  {
3714  "ERROR: Colour depth insufficient to display train colours properly. Please ensure that the 'safe' (web) palette of 256 colours can be displayed");
3715  }
3716  // NB need a separate 'for' loop since the plot order can be different from the graphic order depending on the direction
3717  // of motion
3718  for(int x = 0; x < 4; x++)
3719  {
3720  PlotTrainGraphic(6, x, Disp);
3721  }
3722  BackgroundColour = NewBackgroundColour;
3723  Display->Update();
3724  // need to keep this since Update() not called for PlotSmallOutput as too slow
3725  Utilities->CallLogPop(704);
3726 }
3727 
3728 // ---------------------------------------------------------------------------
3729 
3730 void TTrain::SetTrainMovementValues(int Caller, int TrackVectorPosition, int EntryPos)
3731 /*
3732 Note: Within the loop BrakeRate can only increase and MaxExitSpeed can only reduce
3733 
3734 Summary: Called during PlotStartPosition to set initial values, when stopped and need to restart, and during UpdateTrain when Straddle is LeadMidLag,
3735 i.e. just as the front of a train is about to move fully onto an element, where TrackVectorPosition is the element immediately in front
3736 of the element the front of the train is moving fully on to. The function calculates the times and speeds at the next half-element and
3737 full-element moves.
3738 
3739 Detail: TrackVectorPosition & EntryPos correspond to the TrackVector element immediately in front of where the train is at
3740 the end of the current Update(). EntrySpeed is needed but this is a class data member so isn't passed in. Set the
3741 train BrakeRate to zero (for now, likely to be altered later), & check if zero entry speed with another train directly in front & if so
3742 remain stopped. Pick up the half length value and speed limit for the EntryPos track, and set FrontElementLength to the length of the
3743 EntryPos track, then set LimitingSpeed to the minimum of the element speed limit or the train's maximum speed. Check if running past a
3744 red signal and set SPADFlag if so (use 1 for EntrySpeed rather than 0 as this value is a double so could be slightly in excess of 0).
3745 In this case set the brake rate to maximum to stop as soon as possible.
3746 
3747 For no SPAD calculate the distance that will be travelled at the maximum speed at which the train can exit the next element at half
3748 MaxBrakeRate, this is DistanceAtHalfBraking (also calculate DistanceAtThreeQuarterBraking - used for stopping under signaller control).
3749 DistanceAtHalfBraking is used as the limiting forward look from the next element (i.e. following EntryPos)
3750 for computing the actual braking rate. If no more restrictive speed limits or reasons to stop are found within the forward look then the
3751 train can accelerate or stay at its (local) maximum speed for the next element. The maximum speed on exit from the next element is used
3752 for calculating the forward look because it represents the worst case - i.e. assumes that the train accelerates for the next element.
3753 
3754 A loop is now entered where the CumulativeLength is updated and each successive element (if there are any - current element checked
3755 first to see whether buffers or continuation) in turn is examined: first the length of the
3756 current element is added to the cumulative length; then the half length and speedlimit are set for the next element - points are
3757 followed according to their current setting (Attribute), but derailments are ignored as these are dealt with outside this function; checks
3758 are then made to see whether the next element is a red signal (train should stop before it); next element is a buffer (train should stop
3759 at the end of it so the cumulative length has the next element length added); current element is a buffer (train should stop
3760 at the end of the current element so no need to alter the cumulative length); or have reached a named location stop position. For any of
3761 these reasons, or if stopping under signaller control, there is no more looping, instead the braking rate is calculated to bring the train
3762 to a stop over CumulativeLength. For all normal purposes the braking rate will be less than half (light braking), or less than three
3763 quarters if stopping under signaller control (heavy braking). However if signals are reset in front of a train then the train may need
3764 emergency braking (> 90% max brake rate) and a SPAD may result. Similarly if points are chaged in front of a train that divert it into a
3765 siding then again emeregency braking may be necessary and a crash may result.
3766 
3767 If the train is due to stop then the function calculates the half and full times and speeds and returns. However the calculation depends
3768 on the conditions at entry. If the EntrySpeed is lower than MaxHalfSpeed and the EntryPos element is the one
3769 that the train has to stop at the end of, as it might be for example if train had been stopped at a signal and the next element is a
3770 buffer, then the train accelerates for half the element and brakes for the other half.
3771 Now the BrakeRate is calculated (limited to the MaxBrakeRate), but if it is less than a value calculated at an earlier pass round
3772 the loop then it retains its earlier value (may be due to a close speed restriction that requires more braking than a more distant stop
3773 requirement). The MaxExitSpeedAtHalfBraking (maximum speed at which the train can leave the current element and still stop when required
3774 at half the max braking rate) value is also calculated using EntrySpeed and CumulativeLength, but limiting it to the line speed limit or
3775 train MaxRunningSpeed whichever is the lower. If EntrySpeed > MaxExitSpeedAtHalfBraking then braking is required, so the half and full
3776 speed and time values for the current element are calculated using BrakeRate, EntrySpeed and CurrentElementHalfLength. If need to stop
3777 at the end of the current elemecumulativent for other than a red signal (SPADs can occur) then ExitSpeedFull is set to 0. It should be calculated
3778 as 0 anyway for other than a red signal but this makes sure. If EntrySpeed <= MaxExitSpeedAtHalfBraking then can calculate the half and
3779 full speed and time values for acceleration over the current element, but limit ExitSpeedHalf & Full to MaxExitSpeedAtHalfBraking or to
3780 the current element speed limit if necessary. Check whether ExitSpeedHalf <= EntrySpeed (+0.01 since it's a double) and use constant speed
3781 time values for Half & Full if so, but prior to this increase EntrySpeed if necessary to avoid a divide by zero error.
3782 
3783 If the train is not due to stop within the DistanceAtHalfBraking from the next element following EntryPos then the next element (if there
3784 is one) is checked to see if its speed limit is less than the current value of LimitingSpeed (which is the minimum of any earlier element's
3785 speed limit that has been examined within the loop and the train's MaxRunning speed), and if so LimitingSpeed is set down to it. Now
3786 the MaxExitSpeedAtHalfBraking is calculated, limiting it to LimitingSpeed if less, in case need to accelerate in the current element, in
3787 which case the exit speeds need to be limited to MaxExitSpeedAtHalfBraking. If EntrySpeed > LimitingSpeed then calculate the braking rate
3788 to bring the speed down to LimitingSpeed in CumulativeLength, keeping the existing BrakeRate value if lower and keeping it within
3789 MaxBrakeRate.
3790 
3791 Then, providing the current element isn't a buffer or continuation, the 'Current' values are updated from the 'Next' values ready for
3792 the next loop iteration. The loop is broken out of if the current element is a buffer or continuation, the next element is a
3793 continuation, or (CumulativeLength - FrontElementLength) >= DistanceAtHalfBraking.
3794 
3795 Now the final Half and Full values can be set for braking (if BrakeRate > 0.01), or accelerating - limiting the half and full exit speed
3796 values to MaxExitSpeedAtHalfBraking if necessary, and using constant speed time values if the exit speeds aren't much different to
3797 EntrySpeed and EntrySpeed > 0.01 (to avoid a divide by zero error).
3798 
3799 Note that in no circumstances will a train stop when straddling 3 elements, it will always be fully on two elements. This is ensured
3800 by UpdateTrain() which never sets any stop conditions unless the train is fully on 2 elements when that function returns, i.e. entered
3801 when Straddle == LeadMidLag
3802 */
3803 {
3804  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SetTrainMovementValues," + AnsiString(TrackVectorPosition) + "," +
3805  AnsiString(EntryPos) + "," + HeadCode);
3806  int EntryHalfLength, CurrentElementHalfLength, NextElementHalfLength, CumulativeLength = 0, CurrentTrackVectorPosition = TrackVectorPosition;
3807  int DistanceAtHalfBraking, DistanceAtThreeQuarterBraking, ExitPos, NextTrackVectorPosition, NextEntryPos;
3808  bool RedSignalFlag = false, BuffersFlag = false, StationFlag = false, BuffersOrContinuationNowFlag = false, ContinuationNextFlag = false,
3809  TrainInFrontInSignallerModeFlag = false;
3810  double LimitingSpeed, FrontElementMaxSpeed, MaxExitSpeedAtHalfBrakingSquared, MaxExitSpeedAtHalfBraking, NextSpeedLimit, TempBrakeRate;
3811  double ExitSpeedHalfSquared, ExitSpeedFullSquared;
3812  bool SignallerStopRequired = false;
3813 
3815  // set high to begin with to avoid divide by zero errors on restart after stops, will be set lower later
3816 
3817  // Member variables: EntrySpeed, ExitSpeedHalf, ExitSpeedFull, MaxExitSpeed, BrakeRate, EntryTime, ExitTimeHalf, ExitTimeFull, FrontElementSpeedLimit, FrontElementLength;
3818 
3819  OneLengthAccelDecel = false;
3820  BrakeRate = 0;
3821  if(PowerAtRail < 1)
3822  {
3823  BrakeRate = CoastingBrakeRate; //brings train to a stop in 13km in 15min from starting speed of 100km/h (from research)
3824  }
3825 //find FrontElementLength & FrontElementSpeedLimit (these correspond to TrackVectorPosition input value);
3826  if(CurrentTrackVectorPosition > -1)
3827  {
3828  if(Track->TrackElementAt(855, CurrentTrackVectorPosition).TrackType == Points) // this test & section added at v0.6
3829  {
3830  if((EntryPos == 0) || (EntryPos == 2))
3831  {
3832  if(Track->TrackElementAt(856, CurrentTrackVectorPosition).Attribute == 0)
3833  {
3834  CurrentElementHalfLength = (Track->TrackElementAt(857, CurrentTrackVectorPosition).Length01) / 2;
3835  FrontElementSpeedLimit = Track->TrackElementAt(858, CurrentTrackVectorPosition).SpeedLimit01;
3836  }
3837  else
3838  {
3839  CurrentElementHalfLength = (Track->TrackElementAt(859, CurrentTrackVectorPosition).Length23) / 2;
3840  FrontElementSpeedLimit = Track->TrackElementAt(860, CurrentTrackVectorPosition).SpeedLimit23;
3841  }
3842  }
3843  else if(EntryPos == 1)
3844  {
3845  CurrentElementHalfLength = (Track->TrackElementAt(861, CurrentTrackVectorPosition).Length01) / 2;
3846  FrontElementSpeedLimit = Track->TrackElementAt(862, CurrentTrackVectorPosition).SpeedLimit01;
3847  }
3848  else // == 3
3849  {
3850  CurrentElementHalfLength = (Track->TrackElementAt(863, CurrentTrackVectorPosition).Length23) / 2;
3851  FrontElementSpeedLimit = Track->TrackElementAt(864, CurrentTrackVectorPosition).SpeedLimit23;
3852  }
3853  }
3854  else
3855  {
3856  if(EntryPos > 1)
3857  {
3858  CurrentElementHalfLength = (Track->TrackElementAt(348, CurrentTrackVectorPosition).Length23) / 2;
3859  FrontElementSpeedLimit = Track->TrackElementAt(349, CurrentTrackVectorPosition).SpeedLimit23;
3860  }
3861  else
3862  {
3863  CurrentElementHalfLength = (Track->TrackElementAt(350, CurrentTrackVectorPosition).Length01) / 2;
3864  FrontElementSpeedLimit = Track->TrackElementAt(351, CurrentTrackVectorPosition).SpeedLimit01;
3865  }
3866  }
3867  EntryHalfLength = CurrentElementHalfLength;
3868  FrontElementLength = 2 * CurrentElementHalfLength;
3869  }
3870  else
3871  {
3872  throw Exception("Error - CurrentTrackVectorPosition < 0 in SetTrainMovementValues");
3873  }
3874  if((CurrentElementHalfLength < 0) || (FrontElementSpeedLimit < 0))
3875  {
3876  throw Exception("Error - HalfLength or SpeedLimit < 0 in SetTrainMovementValues");
3877  }
3878  // check if zero entry speed with another train directly in front & if so remain stopped
3879  if((EntryPos > -1) && Track->OtherTrainOnTrack(2, CurrentTrackVectorPosition, EntryPos, TrainID) && (EntrySpeed < 1))
3880  {
3881  EntrySpeed = 0;
3882  ExitSpeedHalf = 0;
3883  ExitSpeedFull = 0;
3884  MaxExitSpeed = 0;
3885  BrakeRate = 0;
3886  ExitTimeHalf = EntryTime + TDateTime(1/24); //set this high in case used later though unlikely
3887  ExitTimeFull = EntryTime + TDateTime(1/23); //set about 2.5 mins later than half time
3888  StoppedForTrainInFront = true;
3889  TrainInFront = true;
3890  Utilities->CallLogPop(705);
3891  return;
3892  }
3893  // new at v2.4.0 - check for stopped and zero power
3894  if((EntrySpeed < 1) && PowerAtRail < 1)
3895  {
3896  EntrySpeed = 0;
3897  ExitSpeedHalf = 0;
3898  ExitSpeedFull = 0;
3899  MaxExitSpeed = 0;
3900  BrakeRate = 0;
3901  ExitTimeHalf = EntryTime + TDateTime(1/24); //set this high in case used later though unlikely
3902  ExitTimeFull = EntryTime + TDateTime(1/23); //set about 2.5 mins later than half time
3903  StoppedWithoutPower = true;
3904  Utilities->CallLogPop(2125);
3905  return;
3906  }
3907 //set LimitingSpeed & FrontElementMaxSpeed (internal values)
3908  if(BeingCalledOn)
3909  {
3910  LimitingSpeed = CallOnMaxSpeed;
3911  }
3912  else
3913  {
3914  LimitingSpeed = MaximumSpeedLimit;
3915  }
3916  if(LimitingSpeed > FrontElementSpeedLimit)
3917  {
3918  LimitingSpeed = FrontElementSpeedLimit;
3919  }
3920  if(LimitingSpeed > MaxRunningSpeed) //MaxRunningSpeed is set in AddTrain depending on timetable or signaller control mode
3921  {
3922  LimitingSpeed = MaxRunningSpeed;
3923  }
3924  FrontElementMaxSpeed = LimitingSpeed;
3925 
3926 /*
3927  for braking the deceleration rate is constant so the following formuli (Newton's Laws) are used:-
3928  (1) V^2/(3.6^2) = U^2/(3.6^2) - 2FS;
3929  (2) V/3.6 = U/3.6 - FT;
3930  (3) S = UT/3.6 - 0.5FT^2
3931  where(V = final speed in kph [km/h/3.6 = m/s], U = initial speed in km/h, F = deceleration rate in m/s/s, S = distance in m & T = time in secs)
3932 
3933  for accelerating the energy input rate (PowerAtRail) is constant so the following formuli are used:-
3934  (4) V^2/(3.6^2) - U^2/(3.6^2) = A^2T;
3935  (5) V = 3.6 * ((1.5*S*A^2) + U^3/ (3.6)^3)^0.333334;
3936  where A is a constant (2*PowerAtRail/Mass)^0.5; V = final speed in kph, U = initial speed in kph , S = distance in m & T = time in secs
3937  It's a bit unrealistic during the early acceleration phase as it will be too rapid, but shouldn't affect the running unduly
3938 
3939  calc max speed that can attain on exit from next element (as could accelerate over next element) and use that speed to calc
3940  DistanceAtHalfBraking, if use actual speed may miss a stop requirement just outside look-ahead & accelerate, and at next calc
3941  be unable to stop or have hard acceleration followed immediately by hard braking, this speed makes for smoother operation
3942 */
3943 
3944 // check if running past a red signal without permission
3945  if((Track->TrackElementAt(352, CurrentTrackVectorPosition).Config[Track->GetNonPointsOppositeLinkPos(EntryPos)] == Signal) &&
3946  (Track->TrackElementAt(353, CurrentTrackVectorPosition).Attribute == 0) && (EntrySpeed > 1) && !AllowedToPassRedSignal &&
3947  !Track->TrackElementAt(1553, CurrentTrackVectorPosition).CallingOnSet)
3948  { //CallingOnSet added at v2.14.0
3949  SPADFlag = true; // user has to intervene to reset & restart after spad
3950  }
3951  if(!SPADFlag)
3952  {
3953  ExitSpeedFull = 3.6 * Power(((3 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)), 0.333334);
3954  // to begin with calc the maximum exit speed (assumes accelerating) and then reduce it if necessary
3955  // for accelerating the energy input rate (PowerAtRail) is constant so the following formuli are used:-
3956  // (1) V^2/(3.6^2) - U^2/(3.6^2) = A^2T; (2) V = 3.6 * ((1.5*S*A^2) + U^3/(3.6^3))^0.333334;
3957  // where A is a constant (2*PowerAtRail/Mass)^0.5; V = final speed in kph, U = initial speed in kph, S = distance & T = time, note that km/h/3.6 = m/s
3958  // This is a bit unrealistic during the early acceleration phase as it will be too rapid, but shouldn't affect the running unduly
3959 
3960  double ExitSpeedAtMaxBraking;
3961  // below introduced at v2.4.0, was ExitSpeedFull = LimitingSpeed; but that allowed very high brake rates when
3962  // took signaller control of a fast failed train with signaller limiting speed 30km/h
3964  {
3965  ExitSpeedAtMaxBraking = 0;
3966  }
3967  else
3968  {
3969  ExitSpeedAtMaxBraking = sqrt((EntrySpeed * EntrySpeed) - 2 * MaxBrakeRate * FrontElementLength);
3970  }
3971  double SpeedToUse;
3972  // use the highest of LimitingSpeed or ExitSpeedAtMaxBraking - added at v2.4.2 because trains entering at a continuation with zero (or very low) speed
3973  // & 2 elements before signal caused ExitSpeedAtMaxBraking & hence DistanceAtHalfBraking and DistanceAtThreeQuarterBraking to be zero, so no restriction was recognised
3974  // for first element & train accelerated at maximum rate, then at 2nd element train couldn't brake in time and overran the signal - notified by Micke via Discord on 02/06/20
3975  if(ExitSpeedAtMaxBraking > LimitingSpeed)
3976  {
3977  SpeedToUse = ExitSpeedAtMaxBraking;
3978  }
3979  else
3980  {
3981  SpeedToUse = LimitingSpeed;
3982  }
3983  if(ExitSpeedFull > SpeedToUse)
3984  {
3985  ExitSpeedFull = SpeedToUse;
3986  }
3987  DistanceAtHalfBraking = ExitSpeedFull * ExitSpeedFull / 3.6 / 3.6 / MaxBrakeRate;
3988  DistanceAtThreeQuarterBraking = ExitSpeedFull * ExitSpeedFull / 3.6 / 3.6 / 1.5 / MaxBrakeRate; // used for signaller stops
3989 
3990  //now enter a do loop to examine each element in turn from the front of the train to calc the cumulative length and to see if a stop is required (flag set if so -
3991  //RedSignalFlag, BuffersFlag, StationFlag, TrainInFrontInSignallerModeFlag, SignallerStopRequired, StepForwardFlag) in which case there are no more loops
3992  // break out of the loop when ((CumulativeLength - FrontElementLength) < DistanceAtHalfBraking ) && ((!BuffersOrContinuationNowFlag && !ContinuationNextFlag) || SignallerStoppingFlag);
3993 
3994  do
3995  {
3996  RedSignalFlag = false;
3997  BuffersFlag = false;
3998  StationFlag = false;
3999  BuffersOrContinuationNowFlag = false;
4000  ContinuationNextFlag = false;
4001  // have to reset this after the above test
4002  // add current element length to CumulativeLength
4003  CumulativeLength += (2 * CurrentElementHalfLength);
4004  if((CumulativeLength >= DistanceAtThreeQuarterBraking) && (TrainMode == Signaller) && SignallerStoppingFlag)
4005  {
4006  SignallerStopRequired = true;
4007  // once set stays set until SignallerStoppingFlag reset, providing !BuffersOrContinuationNowFlag,
4008  // set SignallerStopBrakeRate to stop in CumulativeLength unless already higher (i.e. can only increase)
4009  double TempBR = EntrySpeed * EntrySpeed / 2 / 3.6 / 3.6 / CumulativeLength;
4010  if(SignallerStopBrakeRate < TempBR)
4011  {
4012  SignallerStopBrakeRate = TempBR;
4013  }
4014  }
4015  // first check for stops within the length of the current element, where don't want any more checks & don't want
4016  // to add in any extra to the CumulativeLength. Only applies for buffers & station stops as signals should have been caught
4017  // during the last loop when the NextTrackVectorPosition was the signal.
4018 
4019  // check if current element is a buffer
4020  if(Track->TrackElementAt(374, CurrentTrackVectorPosition).TrackType == Buffers)
4021  {
4022  // no need to add in the length of this element to CumulativeLength as already included
4023  BuffersFlag = true;
4024  }
4025  // check if current element is a station stop
4026  if(TrainMode == Timetable)
4027  {
4028  bool StopRequired = false;
4029  if(!TimetableFinished && (NameInTimetableBeforeCDT(12, Track->TrackElementAt(375, CurrentTrackVectorPosition).ActiveTrackElementName,
4030  StopRequired) > -1) && ((Track->TrackElementAt(376, CurrentTrackVectorPosition).StationEntryStopLinkPos1 == EntryPos) ||
4031  (Track->TrackElementAt(377, CurrentTrackVectorPosition).StationEntryStopLinkPos2 == EntryPos) ||
4032  (Track->TrackElementAt(1642, CurrentTrackVectorPosition).StationEntryStopLinkPos3 == EntryPos) ||
4033  (Track->TrackElementAt(1643, CurrentTrackVectorPosition).StationEntryStopLinkPos4 == EntryPos)))
4034  {
4035  // no need to add in the length of element to CumulativeLength
4036  if(StopRequired)
4037  {
4038  StationFlag = true;
4039  }
4040  }
4041  }
4042  else
4043  {
4044  StationFlag = false;
4045  }
4046  // set NextHalfLength & NextSpeedLimit, but only if current element not buffers or exit continuation - no next element for them
4047  if(((Track->TrackElementAt(354, CurrentTrackVectorPosition).TrackType == Buffers) || (Track->TrackElementAt(355,
4048  CurrentTrackVectorPosition).TrackType == Continuation)) && (EntryPos == 1))
4049  {
4050  BuffersOrContinuationNowFlag = true;
4051  }
4052  if(!BuffersOrContinuationNowFlag && !BuffersFlag && !StationFlag) // skip if buffers or station flags already set
4053  {
4054  if(Track->TrackElementAt(356, CurrentTrackVectorPosition).TrackType == Points)
4055  {
4056  if((EntryPos == 0) || (EntryPos == 2))
4057  {
4058  if(Track->TrackElementAt(357, CurrentTrackVectorPosition).Attribute == 0)
4059  {
4060  ExitPos = 1;
4061  }
4062  else
4063  {
4064  ExitPos = 3;
4065  }
4066  }
4067  else
4068  {
4069  ExitPos = 0;
4070  }
4071  }
4072  else
4073  {
4074  ExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
4075  }
4076  NextTrackVectorPosition = Track->TrackElementAt(358, CurrentTrackVectorPosition).Conn[ExitPos];
4077  NextEntryPos = Track->TrackElementAt(359, CurrentTrackVectorPosition).ConnLinkPos[ExitPos];
4078  if(NextTrackVectorPosition > -1)
4079  {
4080  if(Track->TrackElementAt(845, NextTrackVectorPosition).TrackType == Points)
4081  // this test & section added at v0.6
4082  {
4083  if((NextEntryPos == 0) || (NextEntryPos == 2))
4084  {
4085  if(Track->TrackElementAt(846, NextTrackVectorPosition).Attribute == 0)
4086  {
4087  NextElementHalfLength = (Track->TrackElementAt(847, NextTrackVectorPosition).Length01) / 2;
4088  NextSpeedLimit = Track->TrackElementAt(848, NextTrackVectorPosition).SpeedLimit01;
4089  }
4090  else
4091  {
4092  NextElementHalfLength = (Track->TrackElementAt(849, NextTrackVectorPosition).Length23) / 2;
4093  NextSpeedLimit = Track->TrackElementAt(850, NextTrackVectorPosition).SpeedLimit23;
4094  }
4095  }
4096  else if(NextEntryPos == 1)
4097  {
4098  NextElementHalfLength = (Track->TrackElementAt(851, NextTrackVectorPosition).Length01) / 2;
4099  NextSpeedLimit = Track->TrackElementAt(852, NextTrackVectorPosition).SpeedLimit01;
4100  }
4101  else // == 3
4102  {
4103  NextElementHalfLength = (Track->TrackElementAt(853, NextTrackVectorPosition).Length23) / 2;
4104  NextSpeedLimit = Track->TrackElementAt(854, NextTrackVectorPosition).SpeedLimit23;
4105  }
4106  }
4107  else
4108  {
4109  if(NextEntryPos > 1)
4110  {
4111  NextElementHalfLength = (Track->TrackElementAt(360, NextTrackVectorPosition).Length23) / 2;
4112  NextSpeedLimit = Track->TrackElementAt(361, NextTrackVectorPosition).SpeedLimit23;
4113  }
4114  else
4115  {
4116  NextElementHalfLength = (Track->TrackElementAt(362, NextTrackVectorPosition).Length01) / 2;
4117  NextSpeedLimit = Track->TrackElementAt(363, NextTrackVectorPosition).SpeedLimit01;
4118  }
4119  }
4120  }
4121  else
4122  {
4123  throw Exception("Error - Trying to access NextTrackVectorPosition when none present in SetTrainMovementValues");
4124  }
4125  // now check for stops, first cover those where don't want to add in length of next element
4126  // check if next element is a red signal - Attr 0,
4127  // note that this doesn't apply to trains stopped at a red signal since the signal position is
4128  // CurrentTrackVectorPosition not NextTrackVectorPosition
4129  bool StopRequired;
4130  if(Track->TrackElementAt(364, NextTrackVectorPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal)
4131  {
4132  if(Track->TrackElementAt(365, NextTrackVectorPosition).Attribute == 0)
4133  {
4134  // no need to add in the length of element to CumulativeLength
4135  RedSignalFlag = true;
4136  }
4137  // next element is a red signal
4138  }
4139  // check if current element is a station & next element contains a train - trains will always stop without crashing at a
4140  // station they are due to stop at even if there is a train in front blocking the normal stop position - providing there is
4141  // at least one platform element free
4143  CurrentTrackVectorPosition).ActiveTrackElementName, StopRequired) > -1) && Track->OtherTrainOnTrack(3, NextTrackVectorPosition, NextEntryPos, TrainID))
4144  {
4145  // no need to add in the length of element to CumulativeLength
4146  if(StopRequired)
4147  {
4148  StationFlag = true;
4149  }
4150  }
4151  // check if next element contains a train & in Signaller mode (always stops for train in front if in signaller mode)
4152  else if((TrainMode == Signaller) && Track->OtherTrainOnTrack(4, NextTrackVectorPosition, NextEntryPos, TrainID))
4153  // (Track->TrackElementAt(651, NextTrackVectorPosition).TrainIDOnElement > -1))
4154  {
4155  // no need to add in the length of element to CumulativeLength
4156  TrainInFrontInSignallerModeFlag = true;
4157  }
4158  // check if next element is a buffer, but if StepForwardFlag true then need to stop before reach the buffers
4159  else if((Track->TrackElementAt(366, NextTrackVectorPosition).TrackType == Buffers) && !StepForwardFlag)
4160  {
4161  // need to add in the length of that element to CumulativeLength
4162  CumulativeLength += Track->TrackElementAt(367, NextTrackVectorPosition).Length01;
4163  BuffersFlag = true;
4164  }
4165  // check if next element is a station stop
4167  NextTrackVectorPosition).ActiveTrackElementName, StopRequired) > -1) && ((Track->TrackElementAt(371,
4168  NextTrackVectorPosition).StationEntryStopLinkPos1 == EntryPos) || (Track->TrackElementAt(372,
4169  NextTrackVectorPosition).StationEntryStopLinkPos2 == EntryPos) || (Track->TrackElementAt(1644,
4170  NextTrackVectorPosition).StationEntryStopLinkPos3 == EntryPos) || (Track->TrackElementAt(1645,
4171  NextTrackVectorPosition).StationEntryStopLinkPos4 == EntryPos)))
4172  { // need to add in the length of that element to CumulativeLength if a stop required
4173  if(StopRequired)
4174  {
4175  StationFlag = true;
4176  CumulativeLength += Track->TrackElementAt(373, NextTrackVectorPosition).Length01;
4177  }
4178  }
4179  }
4180  //now can decide whether need to stop over CumulativeLength
4181  if(RedSignalFlag || BuffersFlag || StationFlag || TrainInFrontInSignallerModeFlag || SignallerStopRequired || StepForwardFlag) // no more loops
4182  {
4183  // have to come to a stop over CumulativeLength
4184  if(CumulativeLength == FrontElementLength)
4185  // will be if StepForwardFlag (if stopped to begin with on zero power then earlier check will intercept it and it won't reach here
4186  // only one length to go before stop so check whether need to accelerate for half length then brake for latter
4187  // half; calc speed at halfway point that corresponds to half braking rate for latter half of track element,
4188  // and if less than EntrySpeed then skip this section (don't need any acceleration)
4189  // if not calc speed at halfway point & if less than above set half speed to this value;
4190  // use constant acceleration in calculating half time point
4191  {
4192  MaxExitSpeed = 0;
4193  double MaxHalfSpeed;
4194  double MaxHalfSpeedAtHalfBraking = 3.6 * sqrt(MaxBrakeRate * FrontElementLength / 2);
4195  // have to halve the element length, & can't be zero or negative so no need to test
4196  // if(MaxHalfSpeedAtHalfBraking > LimitingSpeed) MaxHalfSpeed = LimitingSpeed; else MaxHalfSpeed = MaxHalfSpeedAtHalfBraking;
4197  if(MaxHalfSpeedAtHalfBraking > FrontElementMaxSpeed)
4198  {
4199  MaxHalfSpeed = FrontElementMaxSpeed;
4200  }
4201  else
4202  {
4203  MaxHalfSpeed = MaxHalfSpeedAtHalfBraking;
4204  }
4205  if(MaxHalfSpeed > (2 * EntrySpeed) && (PowerAtRail > 1)) //PowerAtRail condition added at v2.18.0
4206  // use 2x to prevent kangarooing at last element when had
4207  // been braking smoothly at less that 50% braking rate, 2x should prevent all but extreme cases
4208  {
4209  ExitSpeedHalf = 3.6 * Power(((1.5 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)),
4210  0.333334);
4211  bool HalfSpeedLimited = false;
4212  if(MaxHalfSpeed < ExitSpeedHalf)
4213  {
4214  ExitSpeedHalf = MaxHalfSpeed;
4215  HalfSpeedLimited = true;
4216  }
4217  if(PowerAtRail > 1)
4218  // added at v2.4.0 in case reach here with failed train, when can't use AValue in denominator as close zero
4219  {
4220  // [km/h/3.6 = m/s]
4221  ExitTimeHalf =
4222  EntryTime + TDateTime(((ExitSpeedHalf * ExitSpeedHalf) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue) / 86400.0);
4223  }
4224  else
4225  {
4226  ExitTimeHalf = EntryTime + TDateTime(EntryHalfLength * 3.6 / EntrySpeed / 86400.0);
4227  }
4228  // the above is the time taken to accelerate to ExitSpeedHalf, so if this is reached before the half
4229  // way point (i.e. HalfSpeedLimited is set) then the time will be too short; change later to equal the
4230  // braking time; not fully accurate but better to be equal than have a short acceleration period followed
4231  // by a long braking period
4232  ExitSpeedFull = 0;
4233  TempBrakeRate = ExitSpeedHalf * ExitSpeedHalf / 2 / 3.6 / 3.6 / EntryHalfLength;
4234  if(TempBrakeRate > MaxBrakeRate)
4235  {
4236  TempBrakeRate = MaxBrakeRate;
4237  }
4238  // shouldn't be but leave in anyway
4239  if(TempBrakeRate > BrakeRate)
4240  {
4241  BrakeRate = TempBrakeRate;
4242  }
4243  // BrakeRate may already have been set in an earlier loop so don't want to reduce it
4244  ExitTimeFull = ExitTimeHalf + TDateTime(ExitSpeedHalf / 3.6 / BrakeRate / 86400.0);
4245  if(HalfSpeedLimited)
4246  // this is the change referred to above
4247  {
4248  TDateTime BrakingTime = ExitTimeFull - ExitTimeHalf;
4249  ExitTimeHalf = EntryTime + BrakingTime;
4250  ExitTimeFull = ExitTimeHalf + BrakingTime;
4251  }
4252  OneLengthAccelDecel = true; //used in TrackTrainFloat in InterfaceUnit.cpp to show accelerating for first half move then decelerating
4253  Utilities->CallLogPop(1095);
4254  return;
4255  }
4256  }
4257  // set braking to achieve speed = 0 @ CumulativeLength up to MaxBrakeRate
4258  // calc MaxExitSpeed for element at EntryPosition & set to this or existing val if lower,
4259  // calc th, tf, sh, & sf
4260  TempBrakeRate = EntrySpeed * EntrySpeed / 2 / 3.6 / 3.6 / CumulativeLength;
4261  if(TempBrakeRate > MaxBrakeRate)
4262  {
4263  TempBrakeRate = MaxBrakeRate;
4264  }
4265  if(TempBrakeRate > BrakeRate)
4266  {
4267  BrakeRate = TempBrakeRate;
4268  }
4269  // BrakeRate may already have been set in an earlier loop so don't want to reduce it
4270  if(SignallerStopRequired)
4271  // set BrakeRate to max of its calculated value or SignallerStopBrakeRate
4272  {
4274  {
4276  // this prevents the brakerate from reducing for a signaller stop
4277  // regardless of other conditions that may change as progress round the loop
4278  }
4279  }
4281  // prevents BrakeRate dropping below SignallerStopBrakeRate once it's been set whether or not SignallerStopRequired set
4282  // SignallerStopRequired may not be set if a red signal found in a later calc, & brakerate may then drop
4283  {
4285  }
4286  int TempMaxExitSpeed;
4287  // calc current value & if less than MaxExitSpeed set that to this
4288  MaxExitSpeedAtHalfBrakingSquared = 3.6 * 3.6 * MaxBrakeRate * (CumulativeLength - FrontElementLength);
4289  if(MaxExitSpeedAtHalfBrakingSquared < 10)
4290  {
4291  MaxExitSpeedAtHalfBraking = 0;
4292  }
4293  else
4294  {
4295  MaxExitSpeedAtHalfBraking = sqrt(MaxExitSpeedAtHalfBrakingSquared);
4296  }
4297  // if(MaxExitSpeedAtHalfBraking > LimitingSpeed) MaxExitSpeed = LimitingSpeed; else MaxExitSpeed = MaxExitSpeedAtHalfBraking;
4298  // I think the above was dropped because it could cause MaxExitSpeed to increase (MaxExitSpeed is an external variable retained between loops)
4299  if(MaxExitSpeedAtHalfBraking > FrontElementMaxSpeed)
4300  {
4301  TempMaxExitSpeed = FrontElementMaxSpeed;
4302  }
4303  else
4304  {
4305  TempMaxExitSpeed = MaxExitSpeedAtHalfBraking;
4306  }
4307  if(TempMaxExitSpeed < MaxExitSpeed)
4308  {
4309  MaxExitSpeed = TempMaxExitSpeed;
4310  }
4311  // here have EntrySpeed & MaxExitSpeed (for the next element), BrakeRate (to bring speed to zero over
4312  // Cumulativelength, and Cumulativelength
4313 
4314  if((EntrySpeed > MaxExitSpeed) || SignallerStopRequired || (SignallerStopBrakeRate > 0.01)) // need to brake
4315  {
4316  ExitSpeedHalfSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 2 * BrakeRate * EntryHalfLength);
4317  if(ExitSpeedHalfSquared < 10)
4318  {
4319  ExitSpeedHalf = 0;
4320  }
4321  else
4322  {
4323  ExitSpeedHalf = sqrt(ExitSpeedHalfSquared);
4324  }
4325  ExitTimeHalf = EntryTime + TDateTime((EntrySpeed - ExitSpeedHalf) / 3.6 / BrakeRate / 86400.0);
4326  ExitSpeedFullSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 4 * BrakeRate * EntryHalfLength);
4327  if(ExitSpeedFullSquared < 10)
4328  {
4329  ExitSpeedFull = 0;
4330  }
4331  else
4332  {
4333  ExitSpeedFull = sqrt(ExitSpeedFullSquared);
4334  }
4335  if((StationFlag) && (CumulativeLength == FrontElementLength))
4336  {
4337  ExitSpeedFull = 0;
4338  // force a stop for station (not for buffers or red signal)
4339  }
4340  ExitTimeFull = EntryTime + TDateTime((EntrySpeed - ExitSpeedFull) / 3.6 / BrakeRate / 86400.0);
4341  }
4342  // new condition at v2.4.0
4343  else if(PowerAtRail < 1)
4344  // use EntrySpeed, CumulativeLength & BrakeRate to calculate the half and full exit times and speeds for next element
4345  // avoid using AValue in denominator or have excessively long times
4346  {
4347  ExitSpeedHalfSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * BrakeRate * FrontElementLength);
4348  ExitSpeedHalf = sqrt(ExitSpeedHalfSquared);
4349  ExitTimeHalf = EntryTime + TDateTime((EntrySpeed - ExitSpeedHalf) / (3.6 * BrakeRate * 86400.0));
4350 
4351  ExitSpeedFullSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 2 * BrakeRate * FrontElementLength);
4352  ExitSpeedFull = sqrt(ExitSpeedFullSquared);
4353  ExitTimeFull = EntryTime + TDateTime((EntrySpeed - ExitSpeedFull) / (3.6 * BrakeRate * 86400.0));
4354  }
4355  else // e.g. moving towards a signal or station after a speed limit, so can accelerate unless no power
4356  // without the power need above condition or have hours of delay times, above added at v2.4.0
4357  {
4358  // for accelerating the energy input rate (PowerAtRail) is constant so the following formuli are used:-
4359  // (1) V^2/(3.6^2) - U^2/(3.6^2) = A^2T; (2) V = 3.6 * ((1.5*S*A^2) + U^3/(3.6^3))^0.333334;
4360  // where A is a constant (2*PowerAtRail/Mass)^0.5; V = final speed in kph, U = initial speed in kph, S = distance & T = time, note that km/h/3.6 = m/s
4361  // This is a bit unrealistic during the early acceleration phase as it will be too rapid, but shouldn't affect the running unduly
4362  BrakeRate = 0;
4363  ExitSpeedHalf = 3.6 * Power(((1.5 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)),
4364  0.333334);
4365  ExitSpeedFull = 3.6 * Power(((3 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)), 0.333334);
4366  // above valid for ExitSpeedHalf & Full <= MaxExitSpeed
4368  // can accelerate continually over the half length
4369  {
4370  ExitTimeHalf = EntryTime + TDateTime(((ExitSpeedHalf * ExitSpeedHalf) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue)
4371  / 86400.0);
4373  // can accelerate continually over the full length
4374  // i.e both (ExitSpeedHalf <= MaxExitSpeed) & (ExitSpeedFull <= MaxExitSpeed)
4375  {
4376  ExitTimeFull =
4377  EntryTime + TDateTime(((ExitSpeedFull * ExitSpeedFull) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue) / 86400.0);
4378  }
4379  else // (ExitSpeedHalf <= MaxExitSpeed) but (ExitSpeedFull > MaxExitSpeed)
4380  // accelerate to MaxExitSpeed then reamin at this speed for rest of element
4381  {
4382  // added at v0.6 as a safeguard
4383  if(MaxExitSpeed < EntrySpeed)
4384  {
4386  }
4387  // to prevent DeltaExitTimeToMaxInSecs being negative
4388  if(MaxExitSpeed < 1)
4389  {
4390  MaxExitSpeed = 1;
4391  }
4392  // to prevent divide by zero error
4393  // below as was
4395  double DeltaExitTimeToMaxInSecs = ((MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue);
4396  double DistanceToMax = ((MaxExitSpeed * MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / 3.6 /
4397  (1.5 * AValue * AValue);
4398  double RemainingDistance = double(FrontElementLength) - DistanceToMax;
4399  double DeltaRemainingTimeInSecs = 3.6 * RemainingDistance / MaxExitSpeed;
4400  ExitTimeFull = EntryTime + TDateTime((DeltaExitTimeToMaxInSecs + DeltaRemainingTimeInSecs) / 86400.0);
4401  }
4402  }
4403  else // ExitSpeedHalf > MaxExitSpeed, so ExitSpeedFull must also be > MaxExitSpeed
4404  // accelerate over first half to MaxExitSpeed then remain at this speed for rest of the first and
4405  // second halves of the element
4406  {
4407  // added at v0.6 as a safeguard
4408  if(MaxExitSpeed < EntrySpeed)
4409  {
4411  }
4412  // to prevent DeltaExitTimeToMaxInSecs being negative
4413  if(MaxExitSpeed < 1)
4414  {
4415  MaxExitSpeed = 1; // to prevent divide by zero error
4416  }
4417  // below as was
4419  double DeltaExitTimeToMaxInSecs = ((MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue);
4420  double DistanceToMax = ((MaxExitSpeed * MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / 3.6 /
4421  (1.5 * AValue * AValue);
4422  double RemainingDistance = double(FrontElementLength / 2) - DistanceToMax;
4423  // remaining distance to half length
4424  double DeltaRemainingTimeInSecs = 3.6 * RemainingDistance / MaxExitSpeed;
4425  ExitTimeHalf = EntryTime + TDateTime((DeltaExitTimeToMaxInSecs + DeltaRemainingTimeInSecs) / 86400.0);
4427  ExitTimeFull = ExitTimeHalf + TDateTime(3.6 * EntryHalfLength / MaxExitSpeed / 86400.0);
4428  }
4429  }
4430  Utilities->CallLogPop(706);
4431  return;
4432  }
4433  else
4434  {
4435  if(!BuffersOrContinuationNowFlag)
4436  {
4437  if(NextSpeedLimit < LimitingSpeed)
4438  {
4439  LimitingSpeed = NextSpeedLimit;
4440  }
4441  }
4442  // calc max exit speed at half braking to ensure don't accelerate past it (if acceleration is required)
4443  int TempMaxExitSpeed;
4444  // calc current value & if less than MaxExitSpeed set that to this
4445  // Note that LimitingSpeed is the max value at the end of CumulativeLength, so MaxExitSpeedAtHalfBrakingSquared will be larger
4446  MaxExitSpeedAtHalfBrakingSquared = (LimitingSpeed * LimitingSpeed) + (3.6 * 3.6 * MaxBrakeRate * (CumulativeLength - FrontElementLength));
4447  if(MaxExitSpeedAtHalfBrakingSquared < 10)
4448  {
4449  MaxExitSpeedAtHalfBraking = 0;
4450  }
4451  else
4452  {
4453  MaxExitSpeedAtHalfBraking = sqrt(MaxExitSpeedAtHalfBrakingSquared);
4454  }
4455  if(MaxExitSpeedAtHalfBraking > FrontElementMaxSpeed)
4456  {
4457  TempMaxExitSpeed = FrontElementMaxSpeed;
4458  }
4459  else
4460  {
4461  TempMaxExitSpeed = MaxExitSpeedAtHalfBraking;
4462  }
4463  if(TempMaxExitSpeed < MaxExitSpeed)
4464  {
4465  MaxExitSpeed = TempMaxExitSpeed;
4466  }
4467  // MaxExitSpeed is an external variable & this can reduce its value
4468  if(EntrySpeed > LimitingSpeed)
4469  // note that LimitingSpeed is more restrictive than MaxExitSpeed
4470  // calc TempBrakeRate & set BrakeRate to this or keep existing val if higher
4471  {
4472  TempBrakeRate = ((EntrySpeed * EntrySpeed) - (LimitingSpeed * LimitingSpeed)) / 3.6 / 3.6 / 2 / CumulativeLength;
4473  if(TempBrakeRate > MaxBrakeRate)
4474  {
4475  TempBrakeRate = MaxBrakeRate;
4476  }
4477  // shouldn't be for speedlimits since all known in advance, but include anyway
4478  if(TempBrakeRate > BrakeRate)
4479  {
4480  BrakeRate = TempBrakeRate;
4481  }
4482  // BrakeRate may already have been set in an earlier loop so don't want to reduce it
4483  }
4484  }
4485  if(!BuffersOrContinuationNowFlag)
4486  {
4487  CurrentTrackVectorPosition = NextTrackVectorPosition;
4488  EntryPos = NextEntryPos;
4489  CurrentElementHalfLength = NextElementHalfLength;
4490  if(Track->TrackElementAt(378, NextTrackVectorPosition).TrackType == Continuation)
4491  {
4492  ContinuationNextFlag = true;
4493  }
4494  }
4495  }
4496  while(((CumulativeLength - FrontElementLength) < DistanceAtHalfBraking) && ((!BuffersOrContinuationNowFlag && !ContinuationNextFlag) ||
4498  // check from the end of the front element, if include the front element and could brake during it, then will skip further loops
4499  // & miss a stop requirement just beyond the front element. happened in Richard Standing's railway where a new service introduced
4500  // on a 100m length, with 20m length after & then a red signal - train accelerated over the 100m then caused a SPAD as too short a
4501  // stopping distance after it.
4502 
4503  //(!BuffersOrContinuationNowFlag && !ContinuationNextFlag) true when no continuation on either the next element and next but one element.
4504  //There won't be a buffer on the next element or would have caught earlier, just using this flag for convenience.
4505 
4506  // If SignallerStoppingFlag then don't exit loop because of an imminent continuation, because continuation
4507  // not immediately in front (if it is then LeadElement will be the continuation & SignallerStoppingFlag will be reset in UpdateTrain()),
4508  // need to at least give a chance to stop on signaller command, if keep moving until continuation is immediately in front then will
4509  // exit loop & that is OK as don't want to stop so close to a continuation, if that happens it means that the command to stop was given
4510  // too late
4511 
4512  // set final braking or acc'n speed & time values
4513  if(BrakeRate > 0.01)
4514  {
4515  ExitSpeedHalfSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 2 * BrakeRate * EntryHalfLength);
4516  if(ExitSpeedHalfSquared < 10)
4517  {
4518  ExitSpeedHalf = 0;
4519  }
4520  else
4521  {
4522  ExitSpeedHalf = sqrt(ExitSpeedHalfSquared);
4523  }
4524  ExitTimeHalf = EntryTime + TDateTime((EntrySpeed - ExitSpeedHalf) / 3.6 / BrakeRate / 86400.0);
4525  ExitSpeedFullSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 4 * BrakeRate * EntryHalfLength);
4526  if(ExitSpeedFullSquared < 10)
4527  {
4528  ExitSpeedFull = 0;
4529  }
4530  else
4531  {
4532  ExitSpeedFull = sqrt(ExitSpeedFullSquared);
4533  }
4534  ExitTimeFull = EntryTime + TDateTime((EntrySpeed - ExitSpeedFull) / 3.6 / BrakeRate / 86400.0);
4535  }
4536  else
4537  {
4538  // for accelerating the energy input rate (PowerAtRail) is constant so the following formuli are used:-
4539  // (1) V^2/(3.6^2) - U^2/(3.6^2) = A^2T; (2) V = 3.6 * ((1.5*S*A^2) + U^3/ (3.6)^3)^0.333334;
4540  // where A is a constant (2*PowerAtRail/Mass)^0.5; V = final speed in kph, U = initial speed in kph , S = distance in m & T = time
4541  // This is a bit unrealistic during the early acceleration phase as it will be too rapid, but shouldn't affect the running unduly
4542 
4543  BrakeRate = 0;
4544  ExitSpeedHalf = 3.6 * Power(((1.5 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)), 0.333334);
4545  ExitSpeedFull = 3.6 * Power(((3 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)), 0.333334);
4546  // above valid for ExitSpeedHalf & Full <= MaxExitSpeed
4548  {
4549  if(PowerAtRail > 1)
4550  // added at v2.4.0 in case reach here with failed train, when can't use AValue in denominator as close zero
4551  {
4552  // [km/h/3.6 = m/s]
4553  ExitTimeHalf = EntryTime + TDateTime(((ExitSpeedHalf * ExitSpeedHalf) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue)
4554  / 86400.0);
4555  }
4556  else
4557  {
4558  ExitTimeHalf = EntryTime + TDateTime(EntryHalfLength * 3.6 / EntrySpeed / 86400.0);
4559  }
4561  // (ExitSpeedHalf <= MaxExitSpeed) & (ExitSpeedFull <= MaxExitSpeed)
4562  {
4563  if(PowerAtRail > 1)
4564  // added at v2.4.0 in case reach here with failed train, when can't use AValue in denominator as close zero
4565  {
4566  // [km/h/3.6 = m/s]
4567  ExitTimeFull = EntryTime + TDateTime(((ExitSpeedFull * ExitSpeedFull) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue)
4568  / 86400.0);
4569  }
4570  else
4571  {
4572  ExitTimeFull = EntryTime + TDateTime(2 * EntryHalfLength * 3.6 / EntrySpeed / 86400.0);
4573  }
4574  }
4575  else // (ExitSpeedHalf <= MaxExitSpeed) & (ExitSpeedFull > MaxExitSpeed)
4576  {
4577  // added at v0.6 as a safeguard
4578  if(MaxExitSpeed < EntrySpeed)
4579  {
4581  }
4582  // to prevent DeltaExitTimeToMaxInSecs being negative
4583  if(MaxExitSpeed < 1)
4584  {
4585  MaxExitSpeed = 1; // to prevent divide by zero error
4586  }
4587  // below as was
4589  double DeltaExitTimeToMaxInSecs;
4590  double DistanceToMax;
4591  if(PowerAtRail > 1) // added at v2.4.0
4592  {
4593  DeltaExitTimeToMaxInSecs = ((MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue);
4594  DistanceToMax = ((MaxExitSpeed * MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / 3.6 /
4595  (1.5 * AValue * AValue);
4596  }
4597  else // shouldn't ever get here given that ExitSpeedFull > ExitSpeedHalf
4598  {
4599  DeltaExitTimeToMaxInSecs = 2 * EntryHalfLength * 3.6 / EntrySpeed;
4600  // these not really accurate but will be good enough
4601  DistanceToMax = EntryHalfLength;
4602  }
4603  double RemainingDistance = double(FrontElementLength) - DistanceToMax;
4604  double DeltaRemainingTimeInSecs = 3.6 * RemainingDistance / MaxExitSpeed;
4605  ExitTimeFull = EntryTime + TDateTime((DeltaExitTimeToMaxInSecs + DeltaRemainingTimeInSecs) / 86400.0);
4606  }
4607  }
4608  else // ExitSpeedHalf > MaxExitSpeed, ExitSpeedFull must be > MaxExitSpeed
4609  {
4610  // added at v0.6 as a safeguard
4611  if(MaxExitSpeed < EntrySpeed)
4612  {
4614  }
4615  // to prevent DeltaExitTimeToMaxInSecs being negative
4616  if(MaxExitSpeed < 1)
4617  {
4618  MaxExitSpeed = 1; // to prevent divide by zero error
4619  }
4620  // below as was
4622  double DeltaExitTimeToMaxInSecs;
4623  double DistanceToMax;
4624  if(PowerAtRail > 1) // added at v2.4.0
4625  {
4626  DeltaExitTimeToMaxInSecs = ((MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue);
4627  DistanceToMax = ((MaxExitSpeed * MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / 3.6 /
4628  (1.5 * AValue * AValue);
4629  }
4630  else
4631  {
4632  DeltaExitTimeToMaxInSecs = 2 * EntryHalfLength * 3.6 / EntrySpeed;
4633  // these not really accurate but will be good enough
4634  DistanceToMax = EntryHalfLength / 2;
4635  }
4636  double RemainingDistance = double(FrontElementLength / 2) - DistanceToMax; // remaining distance to half length
4637  double DeltaRemainingTimeInSecs = 3.6 * RemainingDistance / MaxExitSpeed;
4638  ExitTimeHalf = EntryTime + TDateTime((DeltaExitTimeToMaxInSecs + DeltaRemainingTimeInSecs) / 86400.0);
4640  ExitTimeFull = ExitTimeHalf + TDateTime(3.6 * EntryHalfLength / MaxExitSpeed / 86400.0);
4641  }
4642  }
4643  }
4644 
4645  else // SPADFlag set
4646  {
4648  ExitSpeedHalfSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 2 * BrakeRate * EntryHalfLength);
4649  if(ExitSpeedHalfSquared < 10)
4650  {
4651  ExitSpeedHalf = 0;
4652  }
4653  else
4654  {
4655  ExitSpeedHalf = sqrt(ExitSpeedHalfSquared);
4656  }
4657  ExitTimeHalf = EntryTime + TDateTime((EntrySpeed - ExitSpeedHalf) / 3.6 / BrakeRate / 86400.0);
4658  ExitSpeedFullSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 4 * BrakeRate * EntryHalfLength);
4659  if(ExitSpeedFullSquared < 10)
4660  {
4661  ExitSpeedFull = 0;
4662  }
4663  else
4664  {
4665  ExitSpeedFull = sqrt(ExitSpeedFullSquared);
4666  }
4667  ExitTimeFull = EntryTime + TDateTime((EntrySpeed - ExitSpeedFull) / 3.6 / BrakeRate / 86400.0);
4668 
4669  // check if the exit speed is < 80% of the stopping speed for the next element, and if so stop at end of this element
4670  // this is because would stop short of end of next element (in reality the time to reach the end of the next element
4671  // would be too short (could be so short as to make the train jump) as time is calculated purely on speed & brake rate);
4672  // 80% is used as the brake rate might be set to come to a halt at the end of the next element in which case the speed
4673  // will be the stopping speed.
4674  if(ExitSpeedFull > 0)
4675  {
4676  if(Track->TrackElementAt(746, CurrentTrackVectorPosition).TrackType == Points)
4677  {
4678  if((EntryPos == 0) || (EntryPos == 2))
4679  {
4680  if(Track->TrackElementAt(747, CurrentTrackVectorPosition).Attribute == 0)
4681  {
4682  ExitPos = 1;
4683  }
4684  else
4685  {
4686  ExitPos = 3;
4687  }
4688  }
4689  else
4690  {
4691  ExitPos = 0;
4692  }
4693  }
4694  else
4695  {
4696  ExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
4697  }
4698  NextTrackVectorPosition = Track->TrackElementAt(748, CurrentTrackVectorPosition).Conn[ExitPos];
4699  NextEntryPos = Track->TrackElementAt(749, CurrentTrackVectorPosition).ConnLinkPos[ExitPos];
4700  if(NextTrackVectorPosition > -1) // not a continuation or buffer
4701  {
4702  int NextElementLength;
4703  if(NextEntryPos > 1)
4704  {
4705  NextElementLength = (Track->TrackElementAt(750, NextTrackVectorPosition).Length23);
4706  }
4707  else
4708  {
4709  NextElementLength = (Track->TrackElementAt(751, NextTrackVectorPosition).Length01);
4710  }
4711  double NextStoppingSpeed = sqrt(3.6 * 3.6 * 2 * BrakeRate * NextElementLength);
4712  if(ExitSpeedFull < (0.8 * NextStoppingSpeed))
4713  {
4714  ExitSpeedFull = 0;
4715  }
4716  }
4717  }
4718  }
4719  // allow all values to be set normally in case need to brake, then test for zero power & need to coast
4720  if(PowerAtRail < 1) // new at v2.4.0 note that km/h/3.6 = m/s
4721  {
4722  // bring to a stop in 20 elements at 100km/h & assume each 100m long for calculating exit times but if on a continuation maintain speed <--NO,
4723  //change to BrakeRate = CoastingBrakeRate = 0.03 and calc times etc as normal - because of Albie Vowles' error report of 231223 where noticed
4724  //that failed train treated track lengths of > 2km as 100m so very noticeable. Keep going for exiting at continuation.
4725 
4726  //Coasting deceleration rate from paper 'Real-time train motion parameter estimation using an Unscented Kalman Filter' at
4727  //'https://www.sciencedirect.com/science/article/pii/S0968090X22002212'. In particular Fig 6 in section 4.3 shows coasting from 400sec to
4728  //1000sec corresponds to speed drop from 140km/h to 80km/h, i.e. 60km/h in 600sec, equivalent to 0.02777m/s/s deceleration, so use 0.03m/s/s.
4729 
4730  if(LeadElement > -1)
4731  {
4733  // don't stop on a continuation either entering or leaving
4734  {
4737  MaxExitSpeed = LimitingSpeed;
4738  BrakeRate = 0;
4739  ExitTimeHalf = EntryTime + TDateTime((50 * 3.6 / (EntrySpeed) / 86400.0));
4740  // assume length is 50m for ease of calc
4741  ExitTimeFull = ExitTimeHalf + TDateTime((50 * 3.6 / (ExitSpeedHalf) / 86400.0));
4742  Utilities->CallLogPop(2126);
4743  return;
4744  }
4745  }
4746  else if(MidElement > -1)
4747  {
4749  {
4752  MaxExitSpeed = LimitingSpeed;
4753  BrakeRate = 0;
4754  ExitTimeHalf = EntryTime + TDateTime((50 * 3.6 / (EntrySpeed) / 86400.0));
4755  // assume length is 50m for ease of calc
4756  ExitTimeFull = ExitTimeHalf + TDateTime((50 * 3.6 / (ExitSpeedHalf) / 86400.0));
4757  Utilities->CallLogPop(2127);
4758  return;
4759  }
4760  }
4761  else if(LagElement > -1)
4762  {
4764  {
4767  MaxExitSpeed = LimitingSpeed;
4768  BrakeRate = 0;
4769  ExitTimeHalf = EntryTime + TDateTime((50 * 3.6 / (EntrySpeed) / 86400.0));
4770  // assume length is 50m for ease of calc
4771  ExitTimeFull = ExitTimeHalf + TDateTime((50 * 3.6 / (ExitSpeedHalf) / 86400.0));
4772  Utilities->CallLogPop(2128);
4773  return;
4774  }
4775  }
4776 /* dropped at v2.18.1 in favour of CoastingBrakeRate which = 0.03m/s/s = see above explanation
4777  if(EntrySpeed > 7.5) // keep going for at least another element
4778  {
4779  ExitSpeedHalf = EntrySpeed - 2.5;
4780  ExitSpeedFull = EntrySpeed - 5;
4781  MaxExitSpeed = LimitingSpeed;
4782  BrakeRate = 0;
4783  ExitTimeHalf = EntryTime + TDateTime((50 * 3.6 / (EntrySpeed - 1.25) / 86400.0));
4784  // assume length is 50m for ease of calc
4785  ExitTimeFull = ExitTimeHalf + TDateTime((50 * 3.6 / (ExitSpeedHalf - 1.25) / 86400.0));
4786  Utilities->CallLogPop(2129);
4787  return;
4788  }
4789  else // stop immediately, don't enter next element, floating label had displayed last exit speed full on 2nd half move so with zero when fully on element
4790  {
4791  // will appear to have slowed at steady rate
4792  ExitSpeedHalf = 0;
4793  ExitSpeedFull = 0;
4794  MaxExitSpeed = LimitingSpeed;
4795  BrakeRate = 0;
4796  ExitTimeHalf = EntryTime + TDateTime(1/24); //set this high in case used later though unlikely
4797  ExitTimeFull = EntryTime + TDateTime(1/23); //set about 2.5 mins later than half time
4798  StoppedWithoutPower = true;
4799  Utilities->CallLogPop(2130);
4800  return;
4801  }
4802 */
4803  }
4804  // TempBrakeRate=MinSingle; TempBrakeRate=MaxSingle; TempBrakeRate=MinDouble; TempBrakeRate=MaxDouble;//included to stop warnings from unused declarations in math.hpp
4805  // TempBrakeRate=MinExtended; TempBrakeRate=MaxExtended; TempBrakeRate=MinComp; TempBrakeRate=MaxComp;//included to stop warnings from unused declarations in math.hpp
4806  Utilities->CallLogPop(707);
4807 }
4808 // ---------------------------------------------------------------------------
4809 /*
4810  bool TTrain::IsTerminalStation(int TrackVectorPosition, int EntryPos)
4811  {
4812  int NextExitPos;
4813  TTrackElement NextElement = Track->TrackElementAt(379, TrackVectorPosition), TempElement;
4814  if(TimetableVector.empty()) return false;
4815  if(NextElement.ActiveTrackElementName != TimetableVector.begin()->LocationName) return false;
4816  while((NextElement.ActiveTrackElementName == TimetableVector.begin()->LocationName) && (NextElement.TrackType != Buffers))
4817  {
4818  //check for points & follow attribute, but don't worry about a derail as that dealt with elsewhere
4819  if((NextElement.TrackType != Points) || ((EntryPos != 0) && (EntryPos != 2)))
4820  {
4821  NextExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
4822  }
4823  else if((NextElement.TrackType == Points) && ((EntryPos == 0) || (EntryPos == 2)))
4824  {
4825  if(NextElement.Attribute == 0) NextExitPos = 1; else NextExitPos = 3;
4826  }
4827  TempElement = Track->TrackElementAt(380, NextElement.Conn[NextExitPos]);//need temp as NextElement used in next step
4828  NextElement = TempElement;
4829  }
4830  if(NextElement.ActiveTrackElementName != TimetableVector.begin()->LocationName) return false;
4831  if(NextElement.TrackType == Buffers) return true;
4832  return false;//shouldn't reach here but include to prevent compiler return warning
4833  }
4834 */
4835 // ---------------------------------------------------------------------------
4836 
4837 int TTrain::NameInTimetableBeforeCDT(int Caller, AnsiString Name, bool &Stop)
4838 /*
4839  returns the number by which the train ActionVectorEntryPtr needs
4840  to be incremented to point to the location arrival entry or passtime entry before a change of direction. Used to display missed
4841  actions when a stop or pass location has been reached before other timetabled events have been carried out. If can't find it, or Name
4842  is "", -1 is returned. A change of direction is the limit of the search because a train may not stop at a location on the way out
4843  but stop on way back, and in these circumstances no actions have been missed. Stop indicates whether the train will stop at (true)
4844  or pass (false) the location.
4845 */{
4846  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",NameInTimetableBeforeCDT," + Name + "," + HeadCode);
4847  Stop = false;
4848  if(TimetableFinished || (Name == ""))
4849  {
4850  Utilities->CallLogPop(957);
4851  return(-1);
4852  }
4854 /*added the following check at v2.11.1 because of a fault found in Kevin Smith's railway (notified 02/01/22). CH01 started at
4855 Chester behind the stop position, but when it departed and this function was called it found Chester again with no cdt
4856 before it (it went round the Liverpool Loop), so it stopped again when it reached the stop position and reported all intermediate stops as having been
4857 missed. This check is for the train just having departed from the station in question, and if it has then any further stations
4858 with the same name are ignored - i.e. it stops a train from stopping at the same station twice in succession.
4859 */
4860  if(Ptr > &TrainDataEntryPtr->ActionVector.at(0))
4861  {
4862  Ptr--;
4863  if((Ptr->DepartureTime > TDateTime(-1)) && (Ptr->LocationName == Name))
4864  {
4865  if((Ptr->FormatType == TimeLoc) || (Ptr->FormatType == TimeTimeLoc))
4866  {
4867  Utilities->CallLogPop(2444);
4868  return(-1);
4869  }
4870  }
4871  }
4872  // start looking from current pointer position
4873  for(TActionVectorEntry *Ptr = ActionVectorEntryPtr; Ptr < &TrainDataEntryPtr->ActionVector.back(); Ptr++)
4874  {
4875  if((Ptr->Command == "cdt") || (Ptr->FormatType == Repeat))
4876  {
4877  break;
4878  }
4879  if((Ptr->ArrivalTime > TDateTime(-1)) && (Ptr->LocationName == Name))
4880  {
4881  if((Ptr->FormatType == TimeLoc) || (Ptr->FormatType == TimeTimeLoc))
4882  {
4883  Stop = true;
4884  Utilities->CallLogPop(960);
4885  return (Ptr - ActionVectorEntryPtr);
4886  }
4887  }
4888  if((Ptr->EventTime > TDateTime(-1)) && (Ptr->LocationName == Name) && (Ptr->Command == "pas"))
4889  {
4890  Utilities->CallLogPop(1517);
4891  return (Ptr - ActionVectorEntryPtr);
4892  }
4893  }
4894  Utilities->CallLogPop(959);
4895  return(-1); // not found a valid entry
4896 }
4897 
4898 // ---------------------------------------------------------------------------
4899 
4901 /* Checks forward from train LeadElement, following leading point attributes but ignoring trailing point attributes,
4902  until finds either a train or a signal/buffers/continuation/loop. If finds a train returns false, else returns true.
4903  Ignores the call-on signal.
4904 */{
4905  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ClearToNextSignal" + "," + HeadCode);
4906  int ReturnVal = 0;
4907  int ElementCount = 0;
4908 /* dropped at v2.12.0 as takes up a great deal of time unnecessarily - substitute 1000 elements instead and return true (very unlikely to need to search this far [10km at min length])
4909  for(unsigned int x = 0; x < Track->TrackVector.size(); x++)
4910  {
4911  Track->TrackElementAt(1031, x).TempTrackMarker01 = false;
4912  Track->TrackElementAt(1032, x).TempTrackMarker23 = false;
4913  }
4914 */
4915  int CurrentTrackVectorPosition = LeadElement, NextTrackVectorPosition;
4916  int EntryPos = LeadEntryPos, ExitPos, NextEntryPos;
4917 
4918  while(true)
4919  {
4920  if((Track->TrackElementAt(382, CurrentTrackVectorPosition).TrainIDOnElement > -1) && (Track->TrackElementAt(383,
4921  CurrentTrackVectorPosition).TrainIDOnElement != TrainID))
4922  {
4923  ReturnVal = 1;
4924  break;
4925  }
4926  if(((Track->TrackElementAt(384, CurrentTrackVectorPosition).TrackType == Buffers) || (Track->TrackElementAt(385,
4927  CurrentTrackVectorPosition).TrackType == Continuation)) && (EntryPos == 1))
4928  {
4929  ReturnVal = 2;
4930  break;
4931  }
4932  if((EntryPos < 2) && (Track->TrackElementAt(386, CurrentTrackVectorPosition).Config[1 - EntryPos] == Signal) && !Track->TrackElementAt(529,
4933  CurrentTrackVectorPosition).CallingOnSet && (LeadElement != CurrentTrackVectorPosition)) // CallingOnSet true when position lights lit,
4934  {//added LeadElement condition at v2.18.0 as train may be on the callon signal after CallOnSet false & don't want to return true for that
4935  ReturnVal = 3;
4936  break;
4937  }
4938 /* not needed at and after v2.12.0, see above
4939  if((Track->TrackElementAt(387, CurrentTrackVectorPosition).TrackType == Bridge) || (Track->TrackElementAt(388, CurrentTrackVectorPosition).TrackType == Crossover))
4940  {
4941  if((EntryPos < 2) && (Track->TrackElementAt(523, CurrentTrackVectorPosition).TempTrackMarker01))
4942  // must be a loop - reached same point as examined earlier
4943  {
4944  ReturnVal = 4;
4945  break;
4946  }
4947  else if((EntryPos > 1) && (Track->TrackElementAt(524, CurrentTrackVectorPosition).TempTrackMarker23))
4948  {
4949  ReturnVal = 4;
4950  break;
4951  }
4952  }
4953  else
4954  {
4955  if((Track->TrackElementAt(525, CurrentTrackVectorPosition).TempTrackMarker01) || (Track->TrackElementAt(526, CurrentTrackVectorPosition).TempTrackMarker23))
4956  {
4957  ReturnVal = 4;
4958  break;
4959  }
4960  }
4961  if(EntryPos < 2)
4962  {
4963  Track->TrackElementAt(389, CurrentTrackVectorPosition).TempTrackMarker01 = true;
4964  }
4965  else
4966  {
4967  Track->TrackElementAt(527, CurrentTrackVectorPosition).TempTrackMarker23 = true;
4968  }
4969 */
4970 
4971  if(Track->TrackElementAt(390, CurrentTrackVectorPosition).TrackType == Points)
4972  {
4973  if((EntryPos == 0) || (EntryPos == 2))
4974  {
4975  if(Track->TrackElementAt(391, CurrentTrackVectorPosition).Attribute == 0)
4976  {
4977  ExitPos = 1;
4978  }
4979  else
4980  {
4981  ExitPos = 3;
4982  }
4983  }
4984  else
4985  {
4986  ExitPos = 0;
4987  }
4988  }
4989  else
4990  {
4991  ExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
4992  }
4993  NextTrackVectorPosition = Track->TrackElementAt(392, CurrentTrackVectorPosition).Conn[ExitPos];
4994  NextEntryPos = Track->TrackElementAt(393, CurrentTrackVectorPosition).ConnLinkPos[ExitPos];
4995  CurrentTrackVectorPosition = NextTrackVectorPosition;
4996  EntryPos = NextEntryPos;
4997  ElementCount++;
4998  if(ElementCount > 1000)
4999  {
5000  ReturnVal = 4;
5001  break;
5002  }
5003  }
5004  if(ReturnVal == 1)
5005  {
5006  Utilities->CallLogPop(708);
5007  return(false);
5008  }
5009  if(ReturnVal == 2)
5010  {
5011  Utilities->CallLogPop(709);
5012  return(true);
5013  }
5014  if(ReturnVal == 3)
5015  {
5016  Utilities->CallLogPop(946);
5017  return(true);
5018  }
5019  if(ReturnVal == 4)
5020  {
5021  Utilities->CallLogPop(947);
5022  return(true);
5023  }
5024  else
5025  {
5026  throw Exception("Error - failed to set ReturnVal in ClearToNextSignal()");
5027  }
5028 }
5029 
5030 // ---------------------------------------------------------------------------
5031 
5033 /*
5034  Check whether calling-on conditions met - a) approaching train has stopped at a signal but not at a location;
5035  b) if there is a facing train at the station, it is being held appropriately (must be awaiting a join (Fjo or jbo) or a
5036  change of direction (cdt), remaining here (Frh), or under signaller control);
5037  c) at least 1 platform available for the approaching train; d) points (if any) set for direct route into platform;
5038  e) approaching train is to stop at station; f) no more facing signals between train and platform; g) [dropped g]
5039  h) train in front preventing route being set far enough to release stop signal; i) train in front not exiting at continuation; j) signal must be within 4km of
5040  the stop platform; k) [dropped (k), now can set a reoute or part route into platform so can set points more easily.] l) no existing route conflicts with the route into the platform,
5041  m) not failed or stopped without power
5042  If all OK & route or part route not already set then set an unrestricted route into the station (just to the first platform), to prevent point changing or other route conflicts - if a partial route set than can still
5043  change points outside the route or have a route conflict if another route is set.
5044 */{
5045  if(Track->RouteFlashFlag || TrainFailed || StoppedWithoutPower) //failed & no power conditions added at v2.10.0
5046  {
5047  return(false); // don't want to create a new route from the stop signal if one is already in construction & can't call on if failed or no power
5048  }
5049  // some of the callingon route elements may be involved
5050  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CallingOnAllowed" + "," + HeadCode);
5051  bool PlatformFoundFlag = false, StopRequired = false, SkipRouteCheck = false, RouteOrPartRouteSet = false; // last added at v1.2.0
5052  int CurrentTrackVectorPosition = LeadElement, NextTrackVectorPosition, ElementNumber = 0, Distance = 0;
5053  int RouteStartPosition;
5054  // this is the track vector position of the start element for the new unrestricted route - one past the stop signal
5055  int PlatformPosition;
5056  // the track vector position of the first stop platfrom
5057  int EntryPos = LeadEntryPos, ExitPos, NextEntryPos, RouteID;
5058  // not used here
5059  AnsiString LeadStationName = Track->TrackElementAt(395, LeadElement).ActiveTrackElementName; // still OK even if ""
5060  int LeadElementDistance = Track->TrackElementAt(1017, LeadElement).Length01; //added after 2.7.0 as don't want to add this to overall distance since train has already covered this distance
5061  // use Length01, may be wrong for points/crossovers/bridges but unlikely to occur in practice
5062  // must be stopped at a signal but not at a location & still in timetable (a)
5064  // no need to check for SignallerStopped as this function only called in Timetable mode
5065  {
5066  Utilities->CallLogPop(711);
5067  return(false);
5068  }
5069  while(true)
5070  {
5071  TTrackElement &CurrentTrackElement = Track->TrackElementAt(396, CurrentTrackVectorPosition);
5072  // don't look further than 4km ahead (j)
5073  if(Distance > (4000 + LeadElementDistance))
5074  {
5075  Utilities->CallLogPop(967);
5076  return(false);
5077  }
5078  // if find another train on an element in front, before find a valid platform, return false (c)
5079  if((CurrentTrackElement.TrainIDOnElement > -1) && (CurrentTrackElement.TrainIDOnElement != TrainID) && !PlatformFoundFlag)
5080  {
5081  Utilities->CallLogPop(713);
5082  return(false);
5083  }
5084  // if find another train in front when there is a valid platform (keep searching after find a platform as train may still
5085  // be facing later on)
5086  if((CurrentTrackElement.TrainIDOnElement > -1) && (CurrentTrackElement.TrainIDOnElement != TrainID) && PlatformFoundFlag) //<-can return true in error if other train on bridge
5087  { //on other track, but leave in as shouldn't cause
5088  // get LeadElement, if -1 return (could be exiting at continuation) (i) //any problems - see if anyone reports it and if
5089  TTrain OtherTrain = TrainController->TrainVectorAtIdent(12, CurrentTrackElement.TrainIDOnElement); //so try to correct
5090  if(OtherTrain.LeadElement == -1)
5091  {
5092  Utilities->CallLogPop(714);
5093  return(false);
5094  }
5095  // if a facing train then make sure it is awaiting a join (Fjo or jbo) or a change of direction (cdt), or remaining here (Frh) (b)
5096  if(OtherTrain.LeadElement == CurrentTrackVectorPosition)
5097  {
5098  AnsiString OtherCommand = OtherTrain.ActionVectorEntryPtr->Command;
5099  if((OtherCommand == "Fjo") || (OtherCommand == "jbo") || (OtherCommand == "cdt") || (OtherCommand == "Frh") ||
5100  (OtherTrain.TrainMode == Signaller))
5101  {
5102  break;
5103  }
5104  else
5105  {
5106  Utilities->CallLogPop(955);
5107  return(false);
5108  }
5109  }
5110  else // (h)
5111  {
5112  break;
5113  }
5114  }
5115  // if reach buffers or exit continuation return false (can set route)
5116  if(((CurrentTrackElement.TrackType == Buffers) || (CurrentTrackElement.TrackType == Continuation)) && (EntryPos == 1))
5117  {
5118  Utilities->CallLogPop(716);
5119  return(false);
5120  }
5121  // if reach forward signal (other than the one the train is waiting at) return false (can set route) (f)
5122  if((EntryPos < 2) && (CurrentTrackElement.Config[1 - EntryPos] == Signal) && (CurrentTrackVectorPosition != Track->TrackElementAt(404,
5124  {
5125  Utilities->CallLogPop(717);
5126  return(false);
5127  }
5128  // if reach a location that isn't in timetable return false - drop this as still can't set a route
5129 /*
5130  if((Track->TrackElementAt(405, CurrentTrackVectorPosition).ActiveTrackElementName != "") && (Track->TrackElementAt(406, CurrentTrackVectorPosition).ActiveTrackElementName != LeadStationName) &&
5131  (NameInTimetableBeforeCDT(14, Track->TrackElementAt(407, CurrentTrackVectorPosition).ActiveTrackElementName) == -1))
5132  {
5133  Utilities->CallLogPop(718);
5134  return false;
5135  }
5136 */
5137  // if reach a location that is in timetable set PlatformFoundFlag (but not if position is points set to diverge) (e)
5138  if((CurrentTrackElement.ActiveTrackElementName != "") && (CurrentTrackElement.ActiveTrackElementName != LeadStationName) &&
5139  (NameInTimetableBeforeCDT(15, CurrentTrackElement.ActiveTrackElementName, StopRequired) > -1))
5140  {
5141  if(StopRequired)
5142  {
5143  if((CurrentTrackElement.TrackType != Points) || ((CurrentTrackElement.TrackType == Points) && (CurrentTrackElement.Attribute == 0)))
5144  {
5145  if(!PlatformFoundFlag)
5146  {
5147  PlatformPosition = CurrentTrackVectorPosition;
5148  }
5149  // ensure this only set once at first valid platform position - the unrestricted route will end here
5150  PlatformFoundFlag = true;
5151  }
5152  }
5153  }
5154  // Drop this below - was to prevent call-on if front train had left the station. Criterion now is not that front
5155  // train has to be at station but that has to be before the next forward signal
5156 /*
5157  if((Track->TrackElementAt(411, CurrentTrackVectorPosition).ActiveTrackElementName == "") && (PlatformFoundFlag))
5158  {
5159  Utilities->CallLogPop(719);
5160  return false;
5161  }
5162 */
5163  // make sure points are followed correctly (d) & set ExitPos
5164  if(CurrentTrackElement.TrackType == Points)
5165  {
5166  if((EntryPos == 0) || (EntryPos == 2))
5167  {
5168  if(CurrentTrackElement.Attribute == 0)
5169  {
5170  ExitPos = 1;
5171  }
5172  else
5173  {
5174  ExitPos = 3;
5175  }
5176  }
5177  if(EntryPos == 1)
5178  {
5179  if(CurrentTrackElement.Attribute == 0)
5180  {
5181  ExitPos = 0;
5182  }
5183  else
5184  {
5185  Utilities->CallLogPop(720);
5186  return(false);
5187  }
5188  }
5189  if(EntryPos == 3)
5190  {
5191  if(CurrentTrackElement.Attribute == 1)
5192  {
5193  ExitPos = 0;
5194  }
5195  else
5196  {
5197  Utilities->CallLogPop(721);
5198  return(false);
5199  }
5200  }
5201  }
5202  else
5203  {
5204  ExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
5205  }
5206  // check existing routes - if element forward of the signal (ElementNumber == 2) is AutoSignals then OK without further checks as this route must extend to
5207  // the next signal so must at least reach the station, also if have another route set (must be unrestricted) from either the stop signal or the element after it
5208  // to or towards the platform (& all points set correctly) then OK, otherwise reject if (1) there are any route elements already set from element
5209  // forward of element after the signal to & including the first platform element (covers crossover with other route set) or (2) a fouled diagonal (k)
5210  if(ElementNumber < 2)
5211  {
5212  SkipRouteCheck = true;
5213  }
5214  else
5215  {
5216  SkipRouteCheck = false;
5217  }
5218  if(ElementNumber == 1) // the stop signal
5219  {
5220  RouteStartPosition = CurrentTrackVectorPosition;
5221  }
5222 /*
5223  if(ElementNumber == 2)
5224  {
5225  if(AllRoutes->GetRouteTypeAndNumber(18, CurrentTrackVectorPosition, EntryPos, RouteID) == AllRoutes->AutoSigsRoute) AutoSigs = true;
5226  else AutoSigs = false;
5227  if(AllRoutes->GetRouteTypeAndNumber(25, CurrentTrackVectorPosition, EntryPos, RouteID) == AllRoutes->NotAutoSigsRoute) OtherFullRouteSet = true;
5228  }
5229 */
5230  if(ElementNumber > 1)
5231  {
5232  if(AllRoutes->GetRouteTypeAndNumber(26, CurrentTrackVectorPosition, EntryPos, RouteID) != AllRoutes->NoRoute)
5233  {
5234  RouteOrPartRouteSet = true;
5235  }
5236  else
5237  {
5238  RouteOrPartRouteSet = false;
5239  }
5240  }
5241  if(!SkipRouteCheck && !RouteOrPartRouteSet)
5242  {
5243  if(AllRoutes->TrackIsInARoute(16, CurrentTrackVectorPosition, EntryPos)) // must be a conflicting route
5244  {
5245  Utilities->CallLogPop(1859);
5246  return(false);
5247  }
5248  int ExitLink = CurrentTrackElement.Link[ExitPos];
5249  if((ExitLink == 1) || (ExitLink == 3) || (ExitLink == 7) || (ExitLink == 9))
5250  {
5251  if(AllRoutes->DiagonalFouledByRouteOrTrain(6, CurrentTrackElement.HLoc, CurrentTrackElement.VLoc, ExitLink))
5252  {
5253  Utilities->CallLogPop(1850);
5254  return(false);
5255  }
5256  }
5257  }
5258  // finished all checks, now update CurrentTrackVectorPosition & EntryPos for the next iteration
5259  if(EntryPos < 2)
5260  {
5261  Distance += CurrentTrackElement.Length01;
5262  }
5263  else
5264  {
5265  Distance += CurrentTrackElement.Length23;
5266  }
5267  NextTrackVectorPosition = CurrentTrackElement.Conn[ExitPos];
5268  NextEntryPos = CurrentTrackElement.ConnLinkPos[ExitPos];
5269  CurrentTrackVectorPosition = NextTrackVectorPosition;
5270  EntryPos = NextEntryPos;
5271  ElementNumber++;
5272  } // while(true)
5273 
5274  // if all OK & autosigs route not already set then set an unrestricted route into the station (just to the first platform)
5275  // from the stop signal (note that it may be last in an autosigs route)
5276  // a single element route at the stop signal should have been removed prior to this function being called (that called before
5277  // this in ClockTimer2)
5278 
5279  // now add elements to the CallonVector
5280  TAllRoutes::TCallonEntry CallonEntry(RouteOrPartRouteSet, RouteStartPosition, PlatformPosition);
5281 
5282  AllRoutes->CallonVector.push_back(CallonEntry);
5283  Utilities->CallLogPop(1860);
5284  return(true); // return false if fail to set route for any reason
5285 }
5286 
5287 // ---------------------------------------------------------------------------
5288 /*
5289  bool TTrain::TimetableFinished()
5290  {
5291  if((ActionVectorEntryPtr == TrainDataEntryPtr->ActionVector.end()) || (ActionVectorEntryPtr->FormatType == Repeat))//past all actions
5292  {
5293  return true;
5294  }
5295  return false;
5296  }
5297 */
5298 // ---------------------------------------------------------------------------
5299 
5300 AnsiString TTrain::GetTrainHeadCode(int Caller)
5301 
5302 {
5303  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetTrainHeadCode" + "," + HeadCode);
5304  AnsiString RepeatHeadCode = TrainController->GetRepeatHeadCode(0, HeadCode, RepeatNumber, IncrementalDigits);
5305 
5306  Utilities->CallLogPop(1452);
5307  return(RepeatHeadCode);
5308 }
5309 
5310 // ---------------------------------------------------------------------------
5311 
5312 TDateTime TTrain::GetTrainTime(int Caller, TDateTime Time)
5313 {
5314  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetTrainTime," + Utilities->Format96HHMMSS(Time));
5315  TDateTime RepeatTime = TrainController->GetRepeatTime(1, Time, RepeatNumber, IncrementalMinutes);
5316 
5317  Utilities->CallLogPop(1453);
5318  return(RepeatTime);
5319 }
5320 
5321 // ---------------------------------------------------------------------------
5322 
5323 bool TTrain::IsThereAnAdjacentTrain(int Caller, TTrain *&TrainToBeJoinedBy)
5324 {
5325  // Used to check for a stopped adjacent train for use in PopUp menu //new at v2.4.0
5326  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsThereAnAdjacentTrain" + "," + HeadCode);
5327  // check if there's a stopped adjacent train, if there is but not under sig control give a message in calling function
5328  // first check that train is fully on the railway
5329  bool FrontValid = false, RearValid = false;
5330  TTrackElement FrontAdjacentTrackElement, RearAdjacentTrackElement;
5331 
5332  if((LeadElement == -1) || (MidElement == -1))
5333  {
5334  TrainToBeJoinedBy = NULL;
5335  Utilities->CallLogPop(2131);
5336  return(false);
5337  }
5339  {
5340  FrontAdjacentTrackElement = Track->TrackElementAt(965, (Track->TrackElementAt(966, LeadElement).Conn[LeadExitPos]));
5341  FrontValid = true;
5342  }
5344  {
5345  RearAdjacentTrackElement = Track->TrackElementAt(968, (Track->TrackElementAt(969, MidElement).Conn[MidEntryPos]));
5346  RearValid = true;
5347  }
5348  int TrainToBeJoinedByID = -1;
5349 
5350  // first check if on a 2-track element & select correct ID number if so
5351  if(FrontValid)
5352  {
5353  if(FrontAdjacentTrackElement.TrackType == Bridge)
5354  {
5356  {
5357  TrainToBeJoinedByID = FrontAdjacentTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23;
5358  }
5359  else
5360  {
5361  TrainToBeJoinedByID = FrontAdjacentTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01;
5362  }
5363  }
5364  else
5365  {
5366  TrainToBeJoinedByID = FrontAdjacentTrackElement.TrainIDOnElement;
5367  }
5368  }
5369  if((TrainToBeJoinedByID < 0) && RearValid)
5370  {
5371  // first check if on a 2-track element & select correct ID number if so
5372  if(RearAdjacentTrackElement.TrackType == Bridge)
5373  {
5375  {
5376  TrainToBeJoinedByID = RearAdjacentTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23;
5377  }
5378  else
5379  {
5380  TrainToBeJoinedByID = RearAdjacentTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01;
5381  }
5382  }
5383  else
5384  {
5385  TrainToBeJoinedByID = RearAdjacentTrackElement.TrainIDOnElement;
5386  }
5387  }
5388  if(TrainToBeJoinedByID < 0) // no adjacent train
5389  {
5390  TrainToBeJoinedBy = NULL;
5391  Utilities->CallLogPop(2132);
5392  return(false);
5393  }
5394  TrainToBeJoinedBy = &(TrainController->TrainVectorAtIdent(44, TrainToBeJoinedByID));
5395  if(!TrainToBeJoinedBy->Stopped())
5396  {
5397  TrainToBeJoinedBy = NULL;
5398  Utilities->CallLogPop(2133);
5399  return(false);
5400  }
5401  Utilities->CallLogPop(2134);
5402  return(true);
5403 }
5404 
5405 // ---------------------------------------------------------------------------
5406 
5407 void TTrain::LogAction(int Caller, AnsiString OwnHeadCode, AnsiString OtherHeadCode, TActionType ActionType, AnsiString LocationName, AnsiString SplitDistribution,
5408  TDateTime TimetableNonRepeatTime, bool Warning)
5409 /*
5410  Time = timetable time, the time adjustments for repeat trains is carried out internally
5411  Not all messages need this, if not needed a dummy value is required but not used
5412 
5413  Arrive: 06:05:40: 2F46 arrived at Old Street 1 minute late
5414  Pass: 06:05:40: 2F46 passed Old Street 1 minute late
5415  Terminate: 06:05:40: 2F46 terminated at Old Street 1 minute late <-- sent from RemainHere as LogAction not called for terminate
5416  //NB for Frh just give terminated message but without event time - don't use this function
5417  Depart: 06:05:15: 3F43 departed from Essex Road 2 minutes late
5418  Create: 06:05:40: 2F46 created at Old Street 1 minute late
5419  Enter: 06:05:40: 2F46 entered railway at Old Street 1 minute late
5420  Leave: 06:05:40: 2F46 left railway at 57-N4 1 minute late
5421  FrontSplit: 06:05:40: 2F46 split mass%-Power% = 10-50 from front to 3D54 at Old Street 1 minute late
5422  RearSplit: 06:05:40: 2F46 split mass%-Power% = 10-50 from rear to 3D54 at Old Street 1 minute late
5423  JoinedByOther: 06:05:40: 2F46 joined by 3D54 at Old Street 1 minute late
5424  ChangeDirection: 06:05:40: 2F46 changed direction at Old Street 1 minute late
5425  ChangeDescription: 06:05:40: 2F46 changed its description to 'NewDescription' at Old Street 1 minute late
5426  NewService: 06:05:40: 2F46 became new service 3D54 at Old Street 1 minute late
5427  TakeManualControl: 06:05:40: 2F46 taken under signaller control at Old Street
5428  RestoreTimetableControl: 06:05:40: 2F46 restored to timetable control at Old Street
5429  RemoveTrain: 06:05:40: 2F46 REMOVED FROM RAILWAY DUE TO CRASH at Old Street
5430  RemoveTrain: 06:05:40: 2F46 REMOVED FROM RAILWAY DUE TO DERAILMENT at Old Street
5431  RemoveTrain: 06:05:40: 2F46 REMOVED FROM RAILWAY at Old Street
5432  SignallerMoveForwards 06:05:40: 2F46 received signaller authority to proceed
5433  SignallerChangeDirection 06:05:40: 2F46 changed direction under signaller control at Old Street
5434  SignallerPassRedSignal 06:05:40: 2F46 received signaller authority to pass stop signal
5435  SignallerJoin 06:05:40: 2F46 joined under signaller control by 3D54 at Old Street //new at v2.4.0
5436  TrainFailure 06:05:40: 2F46 suffered an onboard power failure at Old Street //new at v2.4.0
5437  RepairFailedTrain 06:05:40: 2F46 failure repaired at Old Street //new at v2.4.0
5438  SignallerControlStop 06:05:40: 2F46 received signaller instruction to stop
5439  SignallerStop 06:05:40: 2F46 stopped on signaller command
5440  SignallerLeave: 06:05:40: 2F46 left railway under signaller control at 57-N4
5441  SignallerStepForward: 06:05:40: 2F46 received signaller authority to step forward
5442 */{
5443  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LogAction," + OwnHeadCode + "," + OtherHeadCode + "," +
5444  AnsiString(ActionType) + "," + LocationName + "," + HeadCode);
5445  AnsiString BaseLog = "", WarningBaseLog = "", ReminderBaseLog = "", PerfLog = "", ActionLog = "";
5446  int IntMinsLate = 0;
5447  bool TTEvent = false; //indicates a timetabled event, prevents reminders for non-tt events where these set for next tt event
5448  //don't need it for warnings as these passed in from appropriate calling functions
5449  // need to set it in case MinsLate == 0, since it isn't tested for that
5450  if(ActionType == Arrive)
5451  {
5452  ActionLog = " arrived at ";
5453  TTEvent = true;
5454  }
5455  if(ActionType == Terminate) //redundant as Logaction not called for terminate - RemainHere deals with logging for terminate
5456  {
5457  if(TerminatedMessageSent) // to avoid it being sent twice
5458  {
5459  Utilities->CallLogPop(1104);
5460  return;
5461  }
5462  ActionLog = " terminated at ";
5463  TTEvent = true;
5464  TerminatedMessageSent = true;
5465  }
5466  if(ActionType == Depart)
5467  {
5468  ActionLog = " departed from ";
5469  TTEvent = true;
5470  }
5471  if(ActionType == Pass)
5472  {
5473  ActionLog = " passed ";
5474  TTEvent = true;
5475  }
5476  if(ActionType == Create)
5477  {
5478  ActionLog = " created at ";
5479  }
5480  if(ActionType == Enter)
5481  {
5482  ActionLog = " entered railway at ";
5483  }
5484  if(ActionType == ChangeDescription)
5485  {
5486  ActionLog = " changed its description to '" + Description + "' at "; //changed to train description at v2.16.1
5487  TTEvent = true;
5488  }
5489  if(ActionType == Leave)
5490  {
5491  ActionLog = " left railway at ";
5492  TTEvent = true;
5493  }
5494  if(ActionType == FrontSplit)
5495  {
5496  ActionLog = " split mass%-Power% = " + SplitDistribution + " from front to ";
5497  TTEvent = true;
5498  }
5499  if(ActionType == RearSplit)
5500  {
5501  ActionLog = " split mass%-Power% = " + SplitDistribution + " from rear to ";
5502  TTEvent = true;
5503  }
5504  if(ActionType == JoinedByOther)
5505  {
5506  ActionLog = " joined by ";
5507  TTEvent = true;
5508  }
5509  if(ActionType == ChangeDirection)
5510  {
5511  ActionLog = " changed direction at ";
5512  TTEvent = true;
5513  }
5514  if(ActionType == NewService)
5515  {
5516  ActionLog = " became new service ";
5517  TTEvent = true;
5518  }
5519  if(ActionType == TakeSignallerControl)
5520  {
5521  ActionLog = " taken under signaller control at ";
5522  }
5523  if(ActionType == RestoreTimetableControl)
5524  {
5525  ActionLog = " restored to timetable control at ";
5526  }
5527  if(ActionType == RemoveTrain)
5528  {
5529  if(Crashed)
5530  {
5531  ActionLog = " REMOVED FROM RAILWAY DUE TO CRASH at ";
5532  }
5533  else if(Derailed)
5534  {
5535  ActionLog = " REMOVED FROM RAILWAY DUE TO DERAILMENT at ";
5536  }
5537  else
5538  {
5539  ActionLog = " REMOVED FROM RAILWAY at ";
5540  }
5541  }
5542  if(ActionType == SignallerMoveForwards)
5543  {
5544  ActionLog = " received signaller authority to proceed";
5545  }
5546  if(ActionType == SignallerStepForward)
5547  {
5548  ActionLog = " received signaller authority to step forward";
5549  }
5550  if(ActionType == SignallerChangeDirection)
5551  {
5552  ActionLog = " changed direction under signaller control at ";
5553  }
5554  if(ActionType == SignallerPassRedSignal)
5555  {
5556  ActionLog = " received signaller authority to pass stop signal";
5557  }
5558  if(ActionType == SignallerControlStop)
5559  {
5560  ActionLog = " received signaller instruction to stop";
5561  }
5562  if(ActionType == SignallerStop)
5563  {
5564  ActionLog = " stopped on signaller instruction ";
5565  }
5566  if(ActionType == SignallerJoin)
5567  {
5568  ActionLog = " joined under signaller control by ";
5569  }
5570  if(ActionType == TrainFailure)
5571  {
5572  ActionLog = " suffered an onboard power failure at ";
5573  }
5574  if(ActionType == RepairFailedTrain)
5575  {
5576  ActionLog = " failure repaired at ";
5577  }
5578  if(ActionType == SignallerLeave)
5579  {
5580  ActionLog = " left railway under signaller control at ";
5581  }
5582  if(OtherHeadCode != "")
5583  {
5584  OtherHeadCode += " at ";
5585  }
5586  TDateTime ActualTime = TrainController->TTClockTime;
5587 
5588  if(Warning)
5589  {
5590  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " WARNING: " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5591  WarningBaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName; //added time at v2.13.0
5592  }
5593  else //added at v2.19.0
5594  {
5595  if(TTEvent && ((ActionVectorEntryPtr->Reminder == 1) || (ActionVectorEntryPtr->Reminder == 4)))
5596  {
5597  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " REMINDER: " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5598  ReminderBaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName; //added time at v2.13.0
5599  }
5600  else if(TTEvent && (ActionVectorEntryPtr->Reminder == 2) && (ActionLog == " departed from ")) //depart only
5601  {
5602  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " REMINDER: " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5603  ReminderBaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName; //added time at v2.13.0
5604  }
5605  else if(TTEvent && (ActionVectorEntryPtr->Reminder == 3) && (ActionLog == " arrived at ")) //arrive only
5606  {
5607  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " REMINDER: " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5608  ReminderBaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName; //added time at v2.13.0
5609  }
5610  else
5611  {
5612  BaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5613  }
5614  }
5615 
5616  bool TimePerformance = true;
5617 
5618  if((ActionType == TakeSignallerControl) || (ActionType == RestoreTimetableControl) || (ActionType == RemoveTrain) || (ActionType == SignallerMoveForwards)
5619  || (ActionType == SignallerChangeDirection) || (ActionType == SignallerPassRedSignal) || (ActionType == SignallerControlStop) ||
5620  (ActionType == SignallerStop) || (ActionType == SignallerLeave) || (ActionType == SignallerStepForward) || (ActionType == SignallerJoin) ||
5621  (ActionType == TrainFailure) || (ActionType == RepairFailedTrain))
5622  // SignallerJoin & RepairFailedTrain new at v2.4.0
5623  {
5624  TimePerformance = false;
5625  }
5626  if(TimePerformance)
5627  {
5628  double MinsLate = ((double)(ActualTime - GetTrainTime(1, TimetableNonRepeatTime))) * 1440;
5629  MinsDelayed = float(MinsLate);
5630  if(ActionType == Pass) //added at v2.9.2 to prevent time to act increasing suddenly for early pass times then becoming 'NOW' when stops at signal
5631  {
5632  MinsDelayed = 0;
5633  }
5634  // new v2.2.0 for OpActionPanel, can be positive or negative
5635  if(ActionType == Arrive)
5636  {
5638  }
5639  // since train has just arrived this value is the
5640  // recoverable time available at this stop, so reduce MinsDelayed by this amount to prevent it being
5641  // subtracted from later stop recoverable times.
5642  if(MinsLate < 0)
5643  {
5644  IntMinsLate = int(ceil(MinsLate));
5645  }
5646  if(MinsLate > 0)
5647  {
5648  IntMinsLate = int(floor(MinsLate));
5649  }
5650  if(IntMinsLate == 0)
5651  {
5652  PerfLog = " on time";
5653  }
5654  else if(IntMinsLate == 1)
5655  {
5656  PerfLog = " 1 minute late";
5657  }
5658  else if(IntMinsLate == -1)
5659  {
5660  PerfLog = " 1 minute early";
5661  }
5662  else if(IntMinsLate > 1)
5663  {
5664  PerfLog = " " + AnsiString(IntMinsLate) + " minutes late";
5665  }
5666  else if(IntMinsLate < -1)
5667  {
5668  int PosIntMinsLate = -IntMinsLate;
5669  PerfLog = " " + AnsiString(PosIntMinsLate) + " minutes early";
5670  }
5671  if(LocationName.Pos('-') > 0)
5672  {
5673  PerfLog = "," + PerfLog;
5674  // if a position add a comma to separate vertical position number from number of minutes (better appearance)
5675  }
5676  PerfLogForm->PerformanceLog(0, BaseLog + PerfLog);
5677  }
5678  else
5679  {
5680  PerfLogForm->PerformanceLog(1, BaseLog);
5681  }
5682  if(Warning)
5683  {
5684  Display->WarningLog(0, WarningBaseLog);
5685  }
5686  if(ReminderBaseLog != "") //added at v2.19.0
5687  {
5688  Display->WarningLog(24, ReminderBaseLog);
5689  ReminderBaseLog = ""; //reset to null
5690  ActionVectorEntryPtr->Reminder = 0; //to prevent reminders for repeats
5691  }
5692  // update statistics
5693  if((ActionType == Arrive) && (IntMinsLate == 0))
5694  {
5696  }
5697  else if((ActionType == Arrive) && (IntMinsLate > 0))
5698  {
5700  TrainController->TotLateArrMins += IntMinsLate;
5701  }
5702  else if((ActionType == Arrive) && (IntMinsLate < 0))
5703  {
5705  TrainController->TotEarlyArrMins += abs(IntMinsLate);
5706  }
5707 
5708  else if((ActionType == Pass) && (IntMinsLate == 0))
5709  {
5711  }
5712  else if((ActionType == Pass) && (IntMinsLate > 0))
5713  {
5715  TrainController->TotLatePassMins += IntMinsLate;
5716  }
5717  else if((ActionType == Pass) && (IntMinsLate < 0))
5718  {
5720  TrainController->TotEarlyPassMins += abs(IntMinsLate);
5721  }
5722 
5723  else if((ActionType == Leave) && (IntMinsLate == 0)) //new at v2.9.1 as had been omitted in error earlier
5724  {
5726  }
5727  else if((ActionType == Leave) && (IntMinsLate > 0))
5728  {
5730  TrainController->TotLateExitMins += IntMinsLate;
5731  }
5732  else if((ActionType == Leave) && (IntMinsLate < 0))
5733  {
5735  TrainController->TotEarlyExitMins += abs(IntMinsLate);
5736  }
5737 
5738  else if((ActionType == Depart) && (IntMinsLate == 0)) //can't depart early
5739  {
5741  }
5742  else if((ActionType == Depart) && (IntMinsLate > 0))
5743  {
5745  TrainController->TotLateDepMins += IntMinsLate;
5746  }
5747  Utilities->CallLogPop(968);
5748 }
5749 
5750 // ---------------------------------------------------------------------------
5751 
5752 void TTrain::TrainHasFailed(int Caller)
5753 {
5754  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainHasFailed," + HeadCode);
5755  if(Crashed || Derailed || DerailPending)
5756  {
5757  TrainFailurePending = false;
5758  Utilities->CallLogPop(2135);
5759  return;
5760  }
5761  AnsiString LocName = "";
5762 
5763  if(LeadElement > -1)
5764  {
5766  }
5767  if((LocName == "") && (MidElement > -1))
5768  {
5770  }
5771  if((LocName == "") && LeadElement > -1)
5772  {
5773  LocName = Track->TrackElementAt(974, LeadElement).ElementID;
5774  }
5775  if((LocName == "") && (MidElement > -1))
5776  {
5777  LocName = Track->TrackElementAt(975, MidElement).ElementID;
5778  }
5779  TrainController->StopTTClockMessage(81, HeadCode + " has suffered an onboard power failure at " + LocName);
5780  TrainFailed = true;
5781  TrainFailurePending = false;
5782  CallingOnFlag = false; //added at v2.10.0
5784  PowerAtRail = 0.08;
5785  AValue = sqrt(2 * PowerAtRail / Mass);
5787  // TrainFailed only called when PlotElements properly set to Lead, Mid & Lag elements
5788  if(Stopped())
5789  {
5790  EntrySpeed = 0;
5791  ExitSpeedHalf = 0;
5792  ExitSpeedFull = 0;
5793  MaxExitSpeed = 0;
5794  BrakeRate = 0;
5795  StoppedWithoutPower = true;
5796  }
5798  LogAction(33, HeadCode, "", TrainFailure, LocName, "", TDateTime(0), true);
5799  // true for warning, TDateTime not used
5800  Utilities->CallLogPop(2136);
5801 }
5802 
5803 // ---------------------------------------------------------------------------
5804 
5805 void TTrain::FrontTrainSplit(int Caller) //Major rewrite at v2.18.0 using new ThisLocationLongEnoughForSplit
5806 {
5807 /*
5808  Split logic for is:- the final 4 train positions must overlap with the original train, & final 4 positions
5809  will maximise the number at the location. Note that this function isn't sophisticated enough to account for trains already at the
5810  location in determining the 4 positions, and will give a failure message if a train obstructs any of the 4 positions. In these
5811  circumstances the other train will need to be moved sufficiently away to release all 4 positions, then the train will split.
5812 */
5813  TrainController->LogEvent("" + AnsiString(Caller) + ",FrontTrainSplit" + "," + HeadCode);
5814  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",FrontTrainSplit" + "," + HeadCode);
5815 
5816 /* restriction removed at v2.19.0
5817  if(PowerAtRail < 1)
5818  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can split when power restored
5819  {
5820  if(!ZeroPowerNoFrontSplitMessage)
5821  {
5822  TrainController->StopTTClockMessage(82, HeadCode + ": A train without power can't split");
5823  }
5824  ZeroPowerNoFrontSplitMessage = true;
5825  Utilities->CallLogPop(2137);
5826  return;
5827  }
5828 */
5829 
5830  AnsiString LocationName = Track->TrackElementAt(555, LeadElement).ActiveTrackElementName;
5831 
5832  if(LocationName == "")
5833  {
5834  LocationName = Track->TrackElementAt(837, MidElement).ActiveTrackElementName;
5835  }
5836  int RearTrainRearPos, RearTrainFrontPos, RearTrainExitPos;
5837  int FrontTrainRearPos, FrontTrainFrontPos;
5839 
5840  if(LocationName == "")
5841  {
5842  throw Exception("Error - LocationName not set in FrontTrainSplit");
5843  }
5844  // if message given call at ~5 sec intervals in case train repositioned
5845 
5846  bool TemporaryDelay = false;
5848  MidEntryPos, FrontTrainFrontPos, FrontTrainRearPos, RearTrainFrontPos, RearTrainRearPos, TemporaryDelay))
5849  {
5851  {
5853  {
5854  TrainController->StopTTClockMessage(151, HeadCode + " failed to split - location too short at " + LocationName + ", reposition train if possible.");
5855  TrainController->LogActionError(6, HeadCode, "", FailLocTooShort, LocationName);
5857  }
5858  }
5859  Utilities->CallLogPop(1009); //these were inside above bracket & caused own detected fault on second access as didn't return, moved here at v2.19.0
5860  return; //this has been here for a very long time (at least from 2.0.0) but never reported! Either never reached this point or
5861  } //found & not reported. Same for RearTrainSplit
5862 
5863  if(TemporaryDelay)
5864  {
5866  Utilities->CallLogPop(2683);
5867  return;
5868  }
5869 
5870 //it is long enough for split
5871  AnsiString SplitTrainFixedDescription = ActionVectorEntryPtr->LinkedTrainEntryPtr->FixedDescription; //save these for new train before ActionVectorEntryPtr incremented
5872  AnsiString SplittingTrainDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
5873  bool SplitTrainExplicitDescription = ActionVectorEntryPtr->LinkedTrainEntryPtr->ExplicitDescription;
5875 
5876  UnplotTrain(0);
5877  StartSpeed = 0;
5878  RearStartElement = RearTrainRearPos; //this is for the current train, not the new train which will attach to the front of this train
5879  for(int x = 0; x < 4; x++)
5880  {
5881  if(Track->TrackElementAt(1664, RearStartElement).Conn[x] == RearTrainFrontPos)
5882  {
5883  RearStartExitPos = x;
5884  }
5885  }
5886  StoppedAtLocation = true;
5887  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0
5888  {
5889  StoppedWithoutPower = true;
5890  }
5891  PlotStartPosition(3);
5894 // ActionVectorEntryPtr++; moved lower down at v2.15.0 because of new section below
5896 
5897  //new at v2.15.0 for unequal split in mass & power
5898  int NewTrainMass;
5899  double NewTrainPowerAtRail;
5901  {
5902  int pos = ActionVectorEntryPtr->SplitDistribution.Pos('-');
5903  int MassPercent = ActionVectorEntryPtr->SplitDistribution.SubString(1, pos - 1).ToInt(); //validity checked during validation
5904  int PowerPercent = ActionVectorEntryPtr->SplitDistribution.SubString(pos + 1, ActionVectorEntryPtr->SplitDistribution.Length() - pos).ToInt();
5905  NewTrainMass = Mass * double(MassPercent)/100.0;
5906  Mass = Mass - NewTrainMass;
5907  NewTrainPowerAtRail = PowerAtRail * double(PowerPercent)/100.0;
5908  if(NewTrainPowerAtRail == 0)
5909  {
5910  NewTrainPowerAtRail = 0.08; //min value represents 0
5911  }
5912  PowerAtRail = PowerAtRail - NewTrainPowerAtRail;
5913  AValue = sqrt(2 * PowerAtRail / Mass);
5914  }
5915  else // same Mass, MaxBrakeRate & PowerAtRail as this train's halved values, and same MaxRunningSpeed as this train
5916  {
5917  Mass = Mass / 2;
5918  NewTrainMass = Mass;
5919  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).Mass = Mass;
5920  // MaxBrakeRate = MaxBrakeRate / 2; this was wrong - want brake rate to stay the same, brake force is halved but that not a train parameter
5921  // and when needed it's calculated from rate & mass - changed at v2.15.0
5922  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).MaxBrakeRate = MaxBrakeRate;
5923  PowerAtRail = PowerAtRail / 2;
5924  NewTrainPowerAtRail = PowerAtRail;
5925  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).PowerAtRail = PowerAtRail;
5926  AValue = sqrt(2 * PowerAtRail / Mass);
5927  // shouldn't change but include in case not set earlier
5928  }
5929 
5930  TActionEventType EventType = NoEvent;
5931  ActionVectorEntryPtr++; //moved here at v2.18.0 to give more chances to split in case points set wrongly initially, also when AddTrain TrainVector
5932  //may be repositioned so all references to this train may be invalid
5933 
5934  if(!TrainController->AddTrain(0, FrontTrainRearPos, FrontTrainFrontPos, OtherHeadCode, 0, NewTrainMass, MaxRunningSpeed, MaxBrakeRate, NewTrainPowerAtRail,
5935  "Timetable", LinkedTrainEntryPtr, RepeatNumber, IncrementalMinutes, IncrementalDigits, SignallerMaxSpeed, false, EventType))
5936  // false for SignallerControl
5937  {
5938  Utilities->CallLogPop(1721); // EventType not used here
5939  // if fails either a throw will have been sent in AddTrain or start position failed prob because of
5940  // another train, in which case a message will have been sent to the perf log, also might well clear later
5941  // when other train moves away
5942  return;
5943  }
5944 
5945  TrainController->TrainVector.back().Description = SplitTrainFixedDescription; //added at v2.16.1, new train takes description from its TrainDataEntry
5946  if(!SplitTrainExplicitDescription) //new at v2.15.0 see above
5947  {
5948 // OldActionVectorEntryPtr->LinkedTrainEntryPtr->Description = OriginalDescription; dropped at v2.16.1
5949  TrainController->TrainVector.back().Description = SplittingTrainDescription; //else takes it from this train's description
5950  }
5951 
5952  // Note data in 'this' now probably invalid as there has been a new addition to the TrainVector, so the train is likely to have a new address, hence make no more changes for the current train
5953  // see mods in UpdateTrain for v1.3.2
5954  TrainController->TrainAdded = true;
5955 
5956  TTrainOperatingData &TTOD = LinkedTrainEntryPtr->TrainOperatingDataVector.at(RepeatNumber); // this is for the newly created train
5957 
5958  TTOD.TrainID = TrainController->TrainVector.back().TrainID;
5959  TTOD.RunningEntry = Running;
5960  Utilities->CallLogPop(998);
5961 }
5962 
5963 // ---------------------------------------------------------------------------
5964 
5965 void TTrain::RearTrainSplit(int Caller) //Major rewrite at v2.18.0 using new ThisLocationLongEnoughForSplit
5966 {
5967 /*
5968  Split logic for is:- the final 4 train positions must overlap with the original train, & final 4 positions
5969  will maximise the number at the location. Note that this function isn't sophisticated enough to account for trains already at the
5970  location in determining the 4 positions, and will give a failure message if a train obstructs any of the 4 positions. In these
5971  circumstances the other train will need to be moved sufficiently away to release all 4 positions, then the train will split.
5972 */
5973  TrainController->LogEvent("" + AnsiString(Caller) + ",RearTrainSplit" + "," + HeadCode);
5974  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RearTrainSplit" + "," + HeadCode);
5975 
5976 /* restriction removed at v2.19.0
5977  if(PowerAtRail < 1)
5978  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can split when power restored
5979  {
5980  if(!ZeroPowerNoRearSplitMessage)
5981  {
5982  TrainController->StopTTClockMessage(176, HeadCode + ": A train without power can't split");
5983  }
5984  ZeroPowerNoRearSplitMessage = true;
5985  Utilities->CallLogPop(2685);
5986  return;
5987  }
5988 */
5989  AnsiString LocationName = Track->TrackElementAt(1676, LeadElement).ActiveTrackElementName;
5990 
5991  if(LocationName == "")
5992  {
5993  LocationName = Track->TrackElementAt(1677, MidElement).ActiveTrackElementName;
5994  }
5995  int RearTrainRearPos, RearTrainFrontPos;
5996  int FrontTrainRearPos, FrontTrainFrontPos, FrontTrainExitPos;
5998 
5999  if(LocationName == "")
6000  {
6001  throw Exception("Error - LocationName not set in RearTrainSplit");
6002  }
6003  // if message given call at ~5 sec intervals in case train repositioned
6004 
6005  bool TemporaryDelay = false;
6007  MidEntryPos, FrontTrainFrontPos, FrontTrainRearPos, RearTrainFrontPos, RearTrainRearPos, TemporaryDelay))
6008  {
6010  {
6012  {
6013  TrainController->StopTTClockMessage(177, HeadCode + " failed to split - location too short at " + LocationName + ", reposition train if possible.");
6014  TrainController->LogActionError(66, HeadCode, "", FailLocTooShort, LocationName);
6016  }
6017  }
6018  Utilities->CallLogPop(2686); //these were inside above bracket & caused own detected fault on second access as didn't return, moved here at v2.19.0
6019  return; //this has been here for a very long time (at least from 2.0.0) but never reported! Either never reached this point or
6020  } //found & not reported. Same for FrontTrainSplit
6021 
6022  if(TemporaryDelay)
6023  {
6025  Utilities->CallLogPop(2684);
6026  return;
6027  }
6028 
6029 //it is long enough for split
6030  AnsiString SplitTrainFixedDescription = ActionVectorEntryPtr->LinkedTrainEntryPtr->FixedDescription; //save these for new train before ActionVectorEntryPtr incremented
6031  AnsiString SplittingTrainDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
6032  bool SplitTrainExplicitDescription = ActionVectorEntryPtr->LinkedTrainEntryPtr->ExplicitDescription;
6034 
6035  UnplotTrain(11);
6036  StartSpeed = 0;
6037  RearStartElement = FrontTrainRearPos; //this is for the current train, not the new train which will attach to the rear of this train
6038  for(int x = 0; x < 4; x++)
6039  {
6040  if(Track->TrackElementAt(1665, RearStartElement).Conn[x] == FrontTrainFrontPos)
6041  {
6042  RearStartExitPos = x;
6043  }
6044  }
6045  StoppedAtLocation = true;
6046  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0
6047  {
6048  StoppedWithoutPower = true;
6049  }
6050  PlotStartPosition(12);
6053 // ActionVectorEntryPtr++; moved lower down at v2.15.0 because of new section below
6055 
6056  //new at v2.15.0 for unequal split in mass & power
6057  int NewTrainMass;
6058  double NewTrainPowerAtRail;
6060  {
6061  int pos = ActionVectorEntryPtr->SplitDistribution.Pos('-');
6062  int MassPercent = ActionVectorEntryPtr->SplitDistribution.SubString(1, pos - 1).ToInt(); //validity checked during validation
6063  int PowerPercent = ActionVectorEntryPtr->SplitDistribution.SubString(pos + 1, ActionVectorEntryPtr->SplitDistribution.Length() - pos).ToInt();
6064  NewTrainMass = Mass * double(MassPercent)/100.0;
6065  Mass = Mass - NewTrainMass;
6066  NewTrainPowerAtRail = PowerAtRail * double(PowerPercent)/100.0;
6067  if(NewTrainPowerAtRail == 0)
6068  {
6069  NewTrainPowerAtRail = 0.08; //min value represents 0
6070  }
6071  PowerAtRail = PowerAtRail - NewTrainPowerAtRail;
6072  AValue = sqrt(2 * PowerAtRail / Mass);
6073  }
6074  else // same Mass, MaxBrakeRate & PowerAtRail as this train's halved values, and same MaxRunningSpeed as this train
6075  {
6076  Mass = Mass / 2;
6077  NewTrainMass = Mass;
6078  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).Mass = Mass;
6079  // MaxBrakeRate = MaxBrakeRate / 2; this was wrong - want brake rate to stay the same, brake force is halved but that not a train parameter
6080  // and when needed it's calculated from rate & mass - changed at v2.15.0
6081  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).MaxBrakeRate = MaxBrakeRate;
6082  PowerAtRail = PowerAtRail / 2;
6083  NewTrainPowerAtRail = PowerAtRail;
6084  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).PowerAtRail = PowerAtRail;
6085  AValue = sqrt(2 * PowerAtRail / Mass);
6086  // shouldn't change but include in case not set earlier
6087  }
6088 
6089  TActionEventType EventType = NoEvent;
6090  ActionVectorEntryPtr++; //moved here at v2.18.0 to give more chances to split in case points set wrongly initially, also when AddTrain TrainVector
6091  //may be repositioned so all references to this train may be invalid
6092 
6093  if(!TrainController->AddTrain(4, RearTrainRearPos, RearTrainFrontPos, OtherHeadCode, 0, NewTrainMass, MaxRunningSpeed, MaxBrakeRate, NewTrainPowerAtRail,
6094  "Timetable", LinkedTrainEntryPtr, RepeatNumber, IncrementalMinutes, IncrementalDigits, SignallerMaxSpeed, false, EventType))
6095  // false for SignallerControl
6096  {
6097  Utilities->CallLogPop(2687); // EventType not used here
6098  // if fails either a throw will have been sent in AddTrain or start position failed prob because of
6099  // another train, in which case a message will have been sent to the perf log, also might well clear later
6100  // when other train moves away
6101  return;
6102  }
6103 
6104  TrainController->TrainVector.back().Description = SplitTrainFixedDescription; //added at v2.16.1, new train takes description from its TrainDataEntry
6105  if(!SplitTrainExplicitDescription) //new at v2.15.0 see above
6106  {
6107 // OldActionVectorEntryPtr->LinkedTrainEntryPtr->Description = OriginalDescription; dropped at v2.16.1
6108  TrainController->TrainVector.back().Description = SplittingTrainDescription; //else takes it from this train's description
6109  }
6110 
6111  // Note data in 'this' now probably invalid as there has been a new addition to the TrainVector, so the train is likely to have a new address, hence make no more changes for the current train
6112  // see mods in UpdateTrain for v1.3.2
6113  TrainController->TrainAdded = true;
6114 
6115  TTrainOperatingData &TTOD = LinkedTrainEntryPtr->TrainOperatingDataVector.at(RepeatNumber); // this is for the newly created train
6116 
6117  TTOD.TrainID = TrainController->TrainVector.back().TrainID;
6118  TTOD.RunningEntry = Running;
6119  Utilities->CallLogPop(2688);
6120 }
6121 
6122 // ---------------------------------------------------------------------------
6123 
6124 void TTrain::FinishJoin(int Caller)
6125 {
6126  if(FinishJoinLogSent == false)
6127  {
6128  TrainController->LogEvent("" + AnsiString(Caller) + ",FinishJoin" + "," + HeadCode);
6129  FinishJoinLogSent = true; // so don't keep logging this event
6130  // don't need to reset it to false after the event as the train is deleted
6131  }
6132  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",FinishJoin" + "," + HeadCode);
6133  if(TrainFailed)
6134  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can join when repaired) Can FinishJoin if zero power & not failed, as for empty stock
6135  {
6137  {
6138  TrainController->StopTTClockMessage(84, HeadCode + ": A failed train can't join another under timetable control");
6139  }
6141  Utilities->CallLogPop(2139);
6142  return;
6143  }
6144  if(TrainGone)
6145  // this means that the train has already joined the other & is awaiting deletion by TrainController
6146  // without this the 'waiting' message can be given since the other train's ActionVectorEntryPtr has moved
6147  // on from jbo & TrainToJoinIsAdjacent returns false
6148  {
6149  Utilities->CallLogPop(1035);
6150  return;
6151  }
6152  TTrain *TrainToJoin;
6154 
6155  if(!TrainToJoinIsAdjacent(0, TrainToJoin))
6156  {
6158  {
6159  // PerfLogForm->PerformanceLog(2, TrainController->TTClockTime.FormatString("hh:nn:ss") + ": " + HeadCode + " waiting to join " + JBOHeadCode + " at " + ActionVectorEntryPtr->LocationName);
6162  }
6163  Utilities->CallLogPop(1030);
6164  return; // keep this here in case need to add code before final return
6165  }
6166  // no need to clear error report flag here, cleared in jbo function
6167  // No need to set TimetableFinished, done in jbo function
6168  Utilities->CallLogPop(1031);
6169 }
6170 
6171 // ---------------------------------------------------------------------------
6172 
6173 void TTrain::JoinedBy(int Caller)
6174 {
6175  if(TrainController->OpTimeToActUpdateCounter == 0) //added at v2.13.2. Use OpTimeToActUpdateCounter for convenience so only issue the event log
6176  //once every second rather than many times. Can't use an event logged flag because there may
6177  //be several trains that are to be joined by others
6178  {
6179  TrainController->LogEvent("" + AnsiString(Caller) + "," + HeadCode + ",Waiting to be joined");
6180  }
6181  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",JoinedBy" + "," + HeadCode);
6182 
6183 /* restriction removed at v2.19.0
6184  if(PowerAtRail < 1)
6185  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can join when power restored)
6186  {
6187  if(!ZeroPowerNoJoinedByMessage)
6188  {
6189  TrainController->StopTTClockMessage(85, HeadCode + ": A train without power can't be joined by another under timetable control");
6190  }
6191  ZeroPowerNoJoinedByMessage = true;
6192  Utilities->CallLogPop(2140);
6193  return;
6194  }
6195 */
6196  TTrain *TrainToBeJoinedBy;
6198 
6199  if(!TrainToBeJoinedByIsAdjacent(0, TrainToBeJoinedBy))
6200  {
6202  {
6203  // PerfLogForm->PerformanceLog(3, TrainController->TTClockTime.FormatString("hh:nn:ss") + ": " + HeadCode + " waiting to be joined by " + FJOHeadCode + " at " + ActionVectorEntryPtr->LocationName);
6206  }
6207  LastActionDelayFlag = true;
6208  // need to update LastActionTime if this train first to arrive as need 30s after
6209  // both adjacent before the join
6210  Utilities->CallLogPop(1032);
6211  return;
6212  }
6213  // here when other train is adjacent
6215  {
6217  // need to update this as need 30s after both adjacent before the join
6218  LastActionDelayFlag = false;
6219  Utilities->CallLogPop(1033);
6220  return;
6221  }
6222  // here when other train is adjacent & 30 secs elapsed since both adjacent
6223 
6224  // set new values for mass etc
6225  if(MaxRunningSpeed > TrainToBeJoinedBy->MaxRunningSpeed)
6226  {
6227  MaxRunningSpeed = TrainToBeJoinedBy->MaxRunningSpeed;
6228  }
6229  double OtherBrakeForce = TrainToBeJoinedBy->MaxBrakeRate * TrainToBeJoinedBy->Mass;
6230  double OwnBrakeForce = MaxBrakeRate * Mass;
6231  double CombinedBrakeRate = (OtherBrakeForce + OwnBrakeForce) / (TrainToBeJoinedBy->Mass + Mass);
6232 
6233  Mass += TrainToBeJoinedBy->Mass;
6234  MaxBrakeRate = CombinedBrakeRate;
6235  PowerAtRail += TrainToBeJoinedBy->PowerAtRail;
6236  AValue = sqrt(2 * PowerAtRail / Mass);
6237 
6239  TrainToBeJoinedBy->TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).EventReported = NoEvent;
6240  TrainToBeJoinedBy->TimetableFinished = true;
6241  TrainToBeJoinedBy->TrainGone = true;
6242  TrainController->LogEvent("" + AnsiString(Caller) + "," + HeadCode + ",Joined By," + FJOHeadCode); //added at v2.13.2 to provide more information
6243  // this will cause other train to be deleted
6244  TrainToBeJoinedBy->JoinedOtherTrainFlag = true;
6248  Utilities->CallLogPop(1034);
6249 }
6250 
6251 // ---------------------------------------------------------------------------
6252 
6253 void TTrain::ChangeTrainDirection(int Caller, bool NoLogFlag)
6254 {
6255  TrainController->LogEvent("" + AnsiString(Caller) + ",ChangeTrainDirection" + "," + HeadCode);
6256  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ChangeTrainDirection" + "," + HeadCode);
6257 
6258  /* restriction removed at v2.19.0
6259  if(PowerAtRail < 1)
6260  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can change direction when power restored)
6261  {
6262  if(!ZeroPowerNoCDTMessage)
6263  {
6264  TrainController->StopTTClockMessage(86, HeadCode + ": A train without power can't change direction under timetable control");
6265  }
6266  ZeroPowerNoCDTMessage = true;
6267  Utilities->CallLogPop(2141);
6268  return;
6269  }
6270 */
6271  TColor TempColour = BackgroundColour;
6272 
6273  UnplotTrain(2);
6276  StartSpeed = 0;
6277  StoppedAtLocation = true;
6278  PlotStartPosition(1);
6279  PlotTrainWithNewBackgroundColour(27, TempColour, Display);
6280  // plot same as was - should always be pale green
6281  if(!NoLogFlag)
6282  {
6285  }
6287 
6288  //now erase a stub route if there is one, added at v2.5.1
6289  //first element of route is now immediately behind the train (i.e. next to MidElement)
6290  if(MidEntryPos >= 0)
6291  {
6292  TTrackElement MidTrackElement = Track->TrackElementAt(996, MidElement);
6293  int FirstRouteElementVecPos = MidTrackElement.Conn[MidEntryPos];
6294  int FirstRouteLinkPos = MidTrackElement.ConnLinkPos[MidEntryPos];
6295  int RouteNumber = -1;
6296  TAllRoutes::TRouteType RouteType = AllRoutes->GetRouteTypeAndNumber(34, FirstRouteElementVecPos, FirstRouteLinkPos, RouteNumber);
6297  if(RouteType == TAllRoutes::NotAutoSigsRoute)
6298  {
6299  TOneRoute &OR = AllRoutes->GetModifiableRouteAt(28, RouteNumber);
6300  int CorrectRouteID = OR.RouteID; //added at v2.13.0 as when last element removed & route removed from vector OR becomes the next route after the erased one and
6301  //elements can continue to be removed from that route
6302  TTrackElement TE = Track->TrackElementAt(997, FirstRouteElementVecPos);
6303 // if((TE.TrackType != SignalPost) && (TE.TrackType != Continuation)) //all autosigs routes have signalpost or continuation at 0 so they are automatically excluded
6304  { //above condition removed v2.17.0 so non-facing signal or continuation doesn't stop route being removed
6305  //if it is a facing signal then it will be detected below and not removed
6306  bool FirstPass = true; //added at v2.8.0
6307  while((OR.PrefDirSize() > 0) && (OR.RouteID == CorrectRouteID)) //remove the route up to but not including the next facing signal, in case a pref dir route extends to another signal
6308  { // && (OR.RouteID == RouteID) added at v2.13.0 to prevent another route having elements removed
6309  TPrefDirElement PDE = OR.GetFixedPrefDirElementAt(247, 0); //these will change at each element removal because OR is a reference to the real route
6310  int TVPos2 = PDE.GetTrackVectorPosition();
6311  if(FirstPass && (TVPos2 != FirstRouteElementVecPos)) //route is not directed away from cdt train, could be a call-on for another train (added at v2.8.0)
6312  {
6313  break;
6314  }
6315  TTrackElement TE2 = Track->TrackElementAt(998, TVPos2);
6317  {
6318  AllRoutes->RemoveRouteElement(22, TE2.HLoc, TE2.VLoc, PDE.GetELink());
6319  }
6320  else
6321  {
6322  break;
6323  }
6324  FirstPass = false;
6325  }
6326  AllRoutes->RebuildRailwayFlag = true;
6327  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode, to replot without stub route
6328  }
6329  }
6330  }
6331  Utilities->CallLogPop(1012);
6332 }
6333 
6334 // ---------------------------------------------------------------------------
6335 
6336 void TTrain::NewTrainService(int Caller, bool NoLogFlag) //, bool NoLogFlag added at v2.12.0 for new service tt skips
6337 // change to new train, give new service message
6338 //same RepeatNumber used for the new service
6339 { //Note that CumulativeDelayedRandMinsOneTrain carried forward from earlier train (parameter added at v2.13.0)
6340  TrainController->LogEvent("" + AnsiString(Caller) + ",NewTrainService" + "," + HeadCode);
6341  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",NewTrainService" + "," + HeadCode);
6342 
6343 /* restriction removed at v2.19.0
6344  if(PowerAtRail < 1)
6345  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can form new service when power restored)
6346  {
6347  if(!ZeroPowerNoNewServiceMessage)
6348  {
6349  TrainController->StopTTClockMessage(87, HeadCode + ": A train without power can't form a new service");
6350  }
6351  ZeroPowerNoNewServiceMessage = true;
6352  Utilities->CallLogPop(2142);
6353  return;
6354  }
6355 */
6357 
6358  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
6359 
6360  if(!NoLogFlag)
6361  {
6363  }
6364  UnplotTrain(3);
6367  StartSpeed = 0;
6372  HeadCode = NewHeadCode;
6374  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
6375  {
6376  Description = OriginalDescription; //changed at v2.16.1 to train description
6377  }
6378  StoppedAtLocation = true;
6379  PlotStartPosition(5);
6381  // pale green
6384  TerminatedMessageSent = false;
6385  Utilities->CallLogPop(1022);
6386 }
6387 
6388 // ---------------------------------------------------------------------------
6389 
6390 void TTrain::RemainHere(int Caller) //added warnings & reminders at v2.19.0 (not sent from LogAction as not called when train terminates)
6391 {
6392  Utilities->CallLog.push_back(Utilities->TimeStamp() + AnsiString(Caller) + ",RemainHere" + "," + HeadCode);
6393  if(RemainHereLogNotSent) // to prevent repeated logs
6394  {
6395  TrainController->LogEvent(Utilities->TimeStamp() + AnsiString(Caller) + ",RemainHere" + "," + HeadCode);
6396  RemainHereLogNotSent = false;
6397  }
6399  {
6400  TDateTime ActualTime = TrainController->TTClockTime;
6401  AnsiString BaseLog = "", Location = ActionVectorEntryPtr->LocationName;
6402  AnsiString PerfLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + " terminated at " + Location;
6404  {
6405  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " WARNING: " + HeadCode + " terminated at " + Location;
6406  Display->WarningLog(25, PerfLog);
6407  PerfLogForm->PerformanceLog(65, BaseLog);
6408  }
6409  else if(ActionVectorEntryPtr->Reminder > 0)
6410  {
6411  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " REMINDER: " + HeadCode + " terminated at " + Location;
6412  Display->WarningLog(26, PerfLog);
6413  PerfLogForm->PerformanceLog(66, BaseLog);
6414  }
6415  else
6416  {
6417  PerfLogForm->PerformanceLog(67, PerfLog);
6418  }
6420  TerminatedMessageSent = true;
6421  }
6422  TimetableFinished = true;
6423  Utilities->CallLogPop(1023);
6424 }
6425 
6426 // ---------------------------------------------------------------------------
6427 
6428 void TTrain::SendMissedActionLogs(int Caller, int IncNum, TActionVectorEntry *Ptr)
6429 /*
6430  Enter with pointer at next expected action, and IncNum the number by which have to increase the pointer
6431  to reach the action that is valid for the train's current position. i.e. IncNum error messages to be sent
6432  except where an action is a departure, starting at the current value for the pointer
6433  If IncNum is -1, then send messages for all remaining actions, including Fer if present
6434  If IncNum is -2, then send messages for all remaining actions, except Fer if present
6435 */{
6436  if((Ptr->Command == "Snt") && Ptr->SignallerControl)
6437  {
6438  return; // if remove train that starts under signaller control no messages needed
6439 
6440  }
6441  TrainController->LogEvent("" + AnsiString(Caller) + ",SendMissedActionLogs," + AnsiString(IncNum) + "," + HeadCode);
6442  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SendMissedActionLogs," + AnsiString(IncNum) + "," + HeadCode);
6443  if(IncNum > 0)
6444  {
6445  for(int x = 0; x < IncNum; x++)
6446  {
6447  if(x > 0)
6448  {
6449  Ptr++;
6450  }
6451  // arrival - no need to test for termination as this section only covers missed actions up to the
6452  // arrival point - may terminate later but that not missed
6453  if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime > TDateTime(-1)))
6454  {
6456  }
6457  // arrival & departure
6458  if(Ptr->FormatType == TimeTimeLoc)
6459  {
6461  }
6462  // departure
6463  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
6464  {
6465  continue; // skip TimeLoc departures, message given for arrivals
6466  }
6467  // pass
6468  else if(Ptr->FormatType == PassTime)
6469  {
6471  }
6472  // split
6473  else if((Ptr->Command == "fsp") || (Ptr->Command == "rsp"))
6474  {
6476  }
6477  // jbo
6478  else if(Ptr->Command == "jbo")
6479  {
6481  }
6482  // dsc
6483  else if(Ptr->Command == "dsc") //new at v2.15.0
6484  {
6486  }
6487  // Errors - have reached a station stop point (before a cdt) during Train->Update() so intervening actions can't
6488  // be starts, finishes or cdt
6489  else if((Ptr->Command == "Fns") || (Ptr->Command == "Frh") || (Ptr->Command == "Fer") || (Ptr->Command == "Fjo") || (Ptr->Command == "Snt") ||
6490  (Ptr->Command == "Sfs") || (Ptr->Command == "Snt-sh") || (Ptr->Command == "Sns") || (Ptr->Command == "Sns-sh") || (Ptr->Command == "Sns-fsh") ||
6491  (Ptr->Command == "cdt") || (Ptr->Command == "Frh-sh") || (Ptr->Command == "Fns-sh") || (Ptr->Command == "F-nshs") ||
6492  (Ptr->FormatType == Repeat))
6493  {
6494  throw Exception("Error - illegal command in SendMissedActionLogs for IncNum = " + AnsiString(IncNum) + ", and command = " + Ptr->Command);
6495  }
6496  }
6497  }
6498  else
6499  {
6500  bool IncludeFER = false;
6501  if(IncNum == -1)
6502  {
6503  IncludeFER = true;
6504  }
6505  while(true) // finish commands & repeats break out of loop
6506  {
6507  // Fer & excluded - send normal exit log to give minutes late or early - no, have already sent an unexpected exit message
6508  if(!IncludeFER && (Ptr->Command == "Fer"))
6509  {
6510  break;
6511  }
6512  // Fer & included
6513  else if(IncludeFER && (Ptr->Command == "Fer"))
6514  {
6516  break;
6517  }
6518  // Repeat
6519  else if(Ptr->FormatType == Repeat)
6520  {
6521  break;
6522  }
6523  // Fjo
6524  else if(Ptr->Command == "Fjo")
6525  {
6527  break;
6528  }
6529  // Frh
6530  else if(Ptr->Command == "Frh")
6531  {
6533  {
6535  TerminatedMessageSent = true;
6536  }
6537  break;
6538  }
6539  // Frh-sh
6540  else if(Ptr->Command == "Frh-sh")
6541  {
6543  {
6545  TerminatedMessageSent = true;
6546  }
6547  break;
6548  }
6549  // Fns, F-nshs, Fns-sh
6550  else if((Ptr->Command == "Fns") || (Ptr->Command == "F-nshs") || (Ptr->Command == "Fns-sh"))
6551  {
6553  break;
6554  }
6555  // end of breakout actions
6556  // arrival
6557  if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime > TDateTime(-1)))
6558  {
6559  if(IsTrainTerminating(1))
6560  {
6562  TerminatedMessageSent = true;
6563  }
6564  else
6565  {
6567  }
6568  }
6569  // arrival & departure
6570  else if(Ptr->FormatType == TimeTimeLoc)
6571  {
6573  }
6574  // departure
6575  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
6576  {
6577  Ptr++;
6578  continue; // skip TimeLoc departures, message given for arrivals
6579  }
6580  // pass
6581  else if(Ptr->FormatType == PassTime)
6582  {
6584  }
6585  // split
6586  else if((Ptr->Command == "fsp") || (Ptr->Command == "rsp"))
6587  {
6589  }
6590  // jbo
6591  else if(Ptr->Command == "jbo")
6592  {
6594  }
6595  // dsc
6596  else if(Ptr->Command == "dsc") //new at v2.15.0
6597  {
6598 // TrainController->LogActionError(65, HeadCode, "", FailMissedDSC, Ptr->LocationName); don't count as a missed event
6599  }
6600  // cdt
6601  else if(Ptr->Command == "cdt")
6602  {
6603 // TrainController->LogActionError(25, HeadCode, "", FailMissedChangeDirection, Ptr->LocationName); //commented out at v2.12.0 as cdts not counted as missed events
6604  }
6605  // Errors
6606  else if((Ptr->Command == "Snt-sh") || (Ptr->Command == "Sfs") || (Ptr->Command == "Sns") || (Ptr->Command == "Sns-sh") ||
6607  (Ptr->Command == "Sns-fsh") || ((Ptr->Command == "Snt") && !Ptr->SignallerControl))
6608  {
6609  throw Exception("Error - illegal command in SendMissedActionLogs for IncNum = " + AnsiString(IncNum) + ", and command = " + Ptr->Command);
6610  }
6611  Ptr++;
6612  }
6613  TimetableFinished = true;
6614  }
6615  Utilities->CallLogPop(1021);
6616 }
6617 
6618 // ---------------------------------------------------------------------------
6619 
6620 bool TTrain::TrainToJoinIsAdjacent(int Caller, TTrain* &TrainToJoin)
6621 // ensure same repeatnumber
6622 {
6623  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainToJoinIsAdjacent" + "," + HeadCode);
6625 
6626  if(TrainToJoinTDEntry->TrainOperatingDataVector.at(RepeatNumber).RunningEntry != Running)
6627  {
6628  Utilities->CallLogPop(1024);
6629  return(false);
6630  }
6631  TrainToJoin = &(TrainController->TrainVectorAtIdent(33, TrainToJoinTDEntry->TrainOperatingDataVector.at(RepeatNumber).TrainID));
6632  if(TrainToJoin->StoppedAtLocation && (TrainToJoin->TrainMode == Timetable) && (TrainToJoin->ActionVectorEntryPtr->Command == "jbo"))
6633  {
6634  if((Track->TrackElementAt(610, LeadElement).Conn[LeadExitPos] == TrainToJoin->LeadElement) || (Track->TrackElementAt(611,
6635  LeadElement).Conn[LeadExitPos] == TrainToJoin->MidElement) || (Track->TrackElementAt(612, MidElement).Conn[MidEntryPos] == TrainToJoin->LeadElement)
6636  || (Track->TrackElementAt(613, MidElement).Conn[MidEntryPos] == TrainToJoin->MidElement))
6637  {
6638  Utilities->CallLogPop(1025);
6639  return(true);
6640  }
6641  }
6642  Utilities->CallLogPop(1026);
6643  return(false);
6644 }
6645 
6646 // ---------------------------------------------------------------------------
6647 
6648 bool TTrain::TrainToBeJoinedByIsAdjacent(int Caller, TTrain* &TrainToBeJoinedBy)
6649 // ensure same repeatnumber
6650 {
6651  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainToBeJoinedByIsAdjacent" + "," + HeadCode);
6652  TTrainDataEntry *TrainToBeJoinedByTDEntry = ActionVectorEntryPtr->LinkedTrainEntryPtr;
6653 
6654  if(TrainToBeJoinedByTDEntry->TrainOperatingDataVector.at(RepeatNumber).RunningEntry != Running)
6655  {
6656  Utilities->CallLogPop(1027);
6657  return(false);
6658  }
6659  TrainToBeJoinedBy = &(TrainController->TrainVectorAtIdent(15, TrainToBeJoinedByTDEntry->TrainOperatingDataVector.at(RepeatNumber).TrainID));
6660  if(TrainToBeJoinedBy->StoppedAtLocation && (TrainToBeJoinedBy->TrainMode == Timetable) && (TrainToBeJoinedBy->ActionVectorEntryPtr->Command == "Fjo"))
6661  {
6662  if((Track->TrackElementAt(614, LeadElement).Conn[LeadExitPos] == TrainToBeJoinedBy->LeadElement) || (Track->TrackElementAt(615,
6663  LeadElement).Conn[LeadExitPos] == TrainToBeJoinedBy->MidElement) || (Track->TrackElementAt(616,
6664  MidElement).Conn[MidEntryPos] == TrainToBeJoinedBy->LeadElement) || (Track->TrackElementAt(617,
6665  MidElement).Conn[MidEntryPos] == TrainToBeJoinedBy->MidElement))
6666  {
6667  Utilities->CallLogPop(1028);
6668  return(true);
6669  }
6670  }
6671  Utilities->CallLogPop(1029);
6672  return(false);
6673 }
6674 
6675 // ---------------------------------------------------------------------------
6676 
6677 void TTrain::NewShuttleFromNonRepeatService(int Caller, bool NoLogFlag) //bool NoLogFlag added at v2.12.0 for new service TT skips)
6678 { //same RepeatNumber (i.e. 0) used for the new shuttle
6679 //Note that CumulativeDelayedRandMinsOneTrain carried forward from earlier train (parameter added at v2.13.0)
6680  TrainController->LogEvent("" + AnsiString(Caller) + ",NewShuttleFromNonRepeatService" + "," + HeadCode);
6681  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",NewShuttleFromNonRepeatService" + "," + HeadCode);
6682 
6683 /* restriction removed at v2.19.0
6684  if(PowerAtRail < 1)
6685  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can for new service when power restored)
6686  {
6687  if(!ZeroPowerNoNewShuttleFromNonRepeatMessage)
6688  {
6689  TrainController->StopTTClockMessage(88, HeadCode + ": A train without power can't form a new service");
6690  }
6691  ZeroPowerNoNewShuttleFromNonRepeatMessage = true;
6692  Utilities->CallLogPop(2143);
6693  return;
6694  }
6695 */
6696  AnsiString NewHeadCode = ActionVectorEntryPtr->NonRepeatingShuttleLinkHeadCode;
6697  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
6698 
6699  if(!NoLogFlag)
6700  {
6702  }
6703  UnplotTrain(4);
6706  StartSpeed = 0;
6711  HeadCode = NewHeadCode;
6713  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
6714  {
6715  Description = OriginalDescription; //changed at v2.16.1 to train description
6716  }
6717  IncrementalMinutes = TrainDataEntryPtr->ActionVector.back().RearStartOrRepeatMins;
6718  IncrementalDigits = TrainDataEntryPtr->ActionVector.back().FrontStartOrRepeatDigits;
6719  StoppedAtLocation = true;
6720  PlotStartPosition(6);
6722  // pale green
6725  TerminatedMessageSent = false;
6726  Utilities->CallLogPop(1078);
6727 }
6728 
6729 // ---------------------------------------------------------------------------
6730 
6731 void TTrain::RepeatShuttleOrRemainHere(int Caller, bool NoLogFlag) //bool NoLogFlag added at v2.12.0 for new service TT skips)
6732 // need to check whether all repeats finished or not
6733 { //Note that CumulativeDelayedRandMinsOneTrain carried forward from earlier train (parameter added at v2.13.0)
6734  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RepeatShuttleOrRemainHere" + "," + HeadCode);
6735  if(RemainHereLogNotSent) // to prevent repeated logs
6736  {
6737  TrainController->LogEvent("" + AnsiString(Caller) + ",RepeatShuttleOrRemainHere" + "," + HeadCode);
6738  RemainHereLogNotSent = false;
6739  }
6741  // finished all repeats
6742  {
6744  { //no need to suppress this LogAction because BecomeNewService won't be available in this case
6747  TerminatedMessageSent = true;
6748  // no need to clear message as no more actions
6749  }
6750  TimetableFinished = true;
6751  Utilities->CallLogPop(1080);
6752  return;
6753  }
6754 
6755 /* restriction removed at v2.19.0
6756  if(PowerAtRail < 1)
6757  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can for new service when power restored)
6758  {
6759  if(!ZeroPowerNoRepeatShuttleMessage)
6760  {
6761  TrainController->StopTTClockMessage(89, HeadCode + ": A train without power can't form a new service");
6762  }
6763  ZeroPowerNoRepeatShuttleMessage = true;
6764  Utilities->CallLogPop(2144);
6765  return;
6766  }
6767 */
6768  int TempRepeatNumber = RepeatNumber + 1;
6769  // need the next repeat value in order to obtain a correct NewHeadCode, but don't increase it
6770  // until after LogAction or the wrong time will be used
6771  AnsiString NewHeadCode = TrainController->GetRepeatHeadCode(6, ActionVectorEntryPtr->OtherHeadCode, TempRepeatNumber, IncrementalDigits);
6772  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
6773 
6774  if(!NoLogFlag)
6775  {
6777  }
6778  RepeatNumber++;
6779  UnplotTrain(5);
6782  StartSpeed = 0;
6787  HeadCode = NewHeadCode;
6789  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
6790  {
6791  Description = OriginalDescription; //changed at v2.16.1 to train description
6792  }
6793  StoppedAtLocation = true;
6794  PlotStartPosition(7);
6796  // pale green
6799  TerminatedMessageSent = false;
6800  Utilities->CallLogPop(1079);
6801 }
6802 
6803 // ---------------------------------------------------------------------------
6804 
6805 void TTrain::RepeatShuttleOrNewNonRepeatService(int Caller, bool NoLogFlag) //bool NoLogFlag added at v2.12.0 for new service TT skips
6806 { //Note that CumulativeDelayedRandMinsOneTrain carried forward from earlier train (parameter added at v2.13.0)
6807  TrainController->LogEvent("" + AnsiString(Caller) + ",RepeatShuttleOrNewNonRepeatService" + "," + HeadCode);
6808  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RepeatShuttleOrNewNonRepeatService" + "," + HeadCode);
6809 
6810 /* restriction removed at v2.19.0
6811  if(PowerAtRail < 1)
6812  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can for new service when power restored)
6813  {
6814  if(!ZeroPowerNoRepeatShuttleOrNewServiceMessage)
6815  {
6816  TrainController->StopTTClockMessage(90, HeadCode + ": A train without power can't form a new service");
6817  }
6818  ZeroPowerNoRepeatShuttleOrNewServiceMessage = true;
6819  Utilities->CallLogPop(2145);
6820  return;
6821  }
6822 */
6824  // finished all repeats
6825  {
6826  AnsiString NewHeadCode = ActionVectorEntryPtr->NonRepeatingShuttleLinkHeadCode;
6827  if(!NoLogFlag)
6828  {
6830  }
6831  RepeatNumber = 0;
6832  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
6833  UnplotTrain(6);
6836  StartSpeed = 0;
6838  TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).TrainID = TrainID; // but note that RepeatNumber = 0
6839  TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).RunningEntry = Running; // but note that RepeatNumber = 0
6841  HeadCode = NewHeadCode;
6843  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
6844  {
6845  Description = OriginalDescription; //changed at v2.16.1 to train description
6846  }
6847  StoppedAtLocation = true;
6848  PlotStartPosition(9);
6852  TerminatedMessageSent = false;
6853  Utilities->CallLogPop(1081);
6854  return;
6855  }
6856  int TempRepeatNumber = RepeatNumber + 1;
6857  // need the next repeat value in order to obtain a correct NewHeadCode, but don't increase it
6858  // until after LogAction or the wrong time will be used
6859  AnsiString NewHeadCode = TrainController->GetRepeatHeadCode(7, ActionVectorEntryPtr->OtherHeadCode, TempRepeatNumber, IncrementalDigits);
6860  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
6861 
6862  if(!NoLogFlag)
6863  {
6865  }
6866  RepeatNumber++;
6867  UnplotTrain(7);
6870  StartSpeed = 0;
6875  HeadCode = NewHeadCode;
6877  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
6878  {
6879  Description = OriginalDescription; //changed at v2.16.1 to train description
6880  }
6881  StoppedAtLocation = true;
6882  PlotStartPosition(8);
6884  // pale green
6887  TerminatedMessageSent = false;
6888  Utilities->CallLogPop(1082);
6889 }
6890 
6891 // ---------------------------------------------------------------------------
6892 
6894 {
6895  // Search ActionVector from the position after the entry value for Ptr to the end, and return true if find a Finish
6896  // entry before Fer or TimeLoc. No point checking for TimeTimeLoc since at a stop location now so a later TimeTimeLoc
6897  // must be preceded by a TimeLoc departure
6898  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsTrainTerminating" + "," + HeadCode);
6899  for(unsigned int x = 1; x < TrainDataEntryPtr->ActionVector.size(); x++)
6900  {
6902  {
6903  if(((ActionVectorEntryPtr + x)->Command == "Fer") || ((ActionVectorEntryPtr + x)->FormatType == TimeLoc))
6904  {
6905  Utilities->CallLogPop(1083);
6906  return(false);
6907  }
6908  else if((ActionVectorEntryPtr + x)->SequenceType == FinishSequence)
6909  {
6910  Utilities->CallLogPop(1084);
6911  return(true);
6912  }
6913  }
6914  }
6915  Utilities->CallLogPop(1085);
6916  return(false);
6917 }
6918 
6919 // ---------------------------------------------------------------------------
6920 
6921 bool TTrain::AbleToMove(int Caller)
6922 {
6923  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",AbleToMove" + "," + HeadCode);
6924  bool Able = true;
6925 
6926  if(Crashed || Derailed || StoppedAtBuffers || StoppedAtSignal || StoppedWithoutPower) // StoppedWithoutPower added at v2.4.0
6927  {
6928  // StoppedForTrainInFront removed as tested below
6929  Utilities->CallLogPop(2146); // added v2.4.0
6930  return(false); // added v2.4.0
6931  }
6932  if(LeadElement > -1)
6933  {
6934  if(Track->TrackElementAt(801, LeadElement).TrackType == Buffers) //moved up here from 'else' below at v2.12.0
6935  {
6936  StoppedForTrainInFront = false;
6937  TrainInFront = false;
6938  // don't set StoppedAtBuffers as (presumably) StoppedAtLocation & leave it at that
6939  Utilities->CallLogPop(2456);
6940  return(false);
6941  }
6942  int FrontPos = Track->TrackElementAt(678, LeadElement).Conn[LeadExitPos];
6943  int FrontEntryPos = Track->TrackElementAt(679, LeadElement).ConnLinkPos[LeadExitPos];
6944  if((FrontPos > -1) && (TrainMode == Signaller) && TrainInFront) //check if train in front still there
6945  {
6946  TTrackElement TrackElement = Track->TrackElementAt(680, FrontPos);
6947  if((TrackElement.TrackType != Bridge) && (TrackElement.TrainIDOnElement == -1))
6948  {
6949  Able = true;
6950  TrainInFront = false;
6951  }
6952  else if((TrackElement.TrackType == Bridge) && (FrontEntryPos < 2) && (TrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 == -1))
6953  {
6954  Able = true;
6955  TrainInFront = false;
6956  }
6957  else if((TrackElement.TrackType == Bridge) && (FrontEntryPos > 1) && (TrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 == -1))
6958  {
6959  Able = true;
6960  TrainInFront = false;
6961  }
6962  else
6963  {
6964  Able = false; //added at v2.12.0 as train still in front so don't want signaller popup options to move available
6965  }
6966  }
6967  }
6968  else // leaving at a continuation so keep going
6969  {
6970  Able = true;
6971  StoppedForTrainInFront = false;
6972  TrainInFront = false;
6973  }
6974  Utilities->CallLogPop(1454);
6975  return(Able);
6976 }
6977 
6978 // ---------------------------------------------------------------------------
6979 
6981 {
6982  // first check if a train immediately in front (may have moved there since this train stopped so StoppedForTrainInFront
6983  // won't be set; if there is a train then set StoppedForTrainInFront
6984  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",AbleToMoveButForSignalOrTrainInFront" + "," + HeadCode);
6985  // addition below for v1.3.2 after Carwyn Thomas fault reported 24/05/15 - need to check if exiting at continuation (LeadElement == -1) as if so fails at VecPos = .....
6986  if(LeadElement == -1) // exiting at continuation
6987  {
6988  Utilities->CallLogPop(2045);
6989  return(false);
6990  }
6991  // end of addition
6992  int VecPos = Track->TrackElementAt(654, LeadElement).Conn[LeadExitPos];
6993  int NextEntryPos = Track->TrackElementAt(655, LeadElement).ConnLinkPos[LeadExitPos];
6994 
6995  if(Track->OtherTrainOnTrack(5, VecPos, NextEntryPos, TrainID))
6996  {
6997  TrainInFront = true;
6998  Utilities->CallLogPop(1455);
6999  return(false);
7000  }
7001  else
7002  {
7003  Utilities->CallLogPop(1456);
7005  // StoppedWithoutPower added v2.4.0
7006  }
7007 }
7008 
7009 // ---------------------------------------------------------------------------
7010 
7012 {
7013  // unplots & replots train, which checks for facing signal and sets StoppedAtSignal if req'd
7014  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SignallerChangeTrainDirection" + "," + HeadCode);
7015  TColor TempColour = BackgroundColour;
7016 
7017  UnplotTrain(8);
7020  StartSpeed = 0;
7021  PlotStartPosition(2);
7022  PlotTrainWithNewBackgroundColour(26, TempColour, Display);
7023 
7024  //now erase a stub route if there is one, added at v2.5.1
7025  //first element of route is now immediately behind the train (i.e. next to MidElement)
7026  if(MidEntryPos >= 0)
7027  {
7028  TTrackElement MidTrackElement = Track->TrackElementAt(1000, MidElement);
7029  int FirstRouteElementVecPos = MidTrackElement.Conn[MidEntryPos];
7030  int FirstRouteLinkPos = MidTrackElement.ConnLinkPos[MidEntryPos];
7031  int RouteNumber = -1;
7032  TAllRoutes::TRouteType RouteType = AllRoutes->GetRouteTypeAndNumber(35, FirstRouteElementVecPos, FirstRouteLinkPos, RouteNumber);
7033  if(RouteType == TAllRoutes::NotAutoSigsRoute)
7034  {
7035  TOneRoute &OR = AllRoutes->GetModifiableRouteAt(29, RouteNumber);
7036  int CorrectRouteID = OR.RouteID; //added at v2.13.0 as when last element removed & route removed from vector OR becomes the next route after the erased one and
7037  //elements can continue to be removed from that route
7038  TTrackElement TE = Track->TrackElementAt(1001, FirstRouteElementVecPos);
7039 // if((TE.TrackType != SignalPost) && (TE.TrackType != Continuation)) //all autosigs routes have signalpost or continuation at 0 so they are automatically excluded
7040  { //above condition removed at v2.17.0 so non-facing signal or continuation doesn't stop route being removed
7041  //if it is a facing signal then it will be detected below and not removed
7042  bool FirstPass = true; //added at v2.8.0
7043  while((OR.PrefDirSize() > 0) && (OR.RouteID == CorrectRouteID)) //remove the route up to but not including the next facing signal, in case a pref dir route extends to another signal
7044  { // && (OR.RouteID == RouteID) added at v2.13.0 to prevent another route having elements removed
7045  TPrefDirElement PDE = OR.GetFixedPrefDirElementAt(248, 0); //these will change at each element removal because OR is a reference to the real route
7046  int TVPos2 = PDE.GetTrackVectorPosition();
7047  if(FirstPass && (TVPos2 != FirstRouteElementVecPos)) //route is not directed away from cdt train, could be a call-on for another train (added at v2.8.0)
7048  {
7049  break;
7050  }
7051  TTrackElement TE2 = Track->TrackElementAt(1002, TVPos2);
7053  {
7054  AllRoutes->RemoveRouteElement(23, TE2.HLoc, TE2.VLoc, PDE.GetELink());
7055  }
7056  else
7057  {
7058  break;
7059  }
7060  FirstPass = false;
7061  }
7062  AllRoutes->RebuildRailwayFlag = true;
7063  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode, to replot without stub route
7064  }
7065  }
7066  }
7067  Utilities->CallLogPop(1102);
7068 }
7069 
7070 // ---------------------------------------------------------------------------
7071 
7073 {
7074  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - &TrainDataEntryPtr->ActionVector.front()) +
7075  ",FloatingLabelNextString" + "," + HeadCode);
7076  AnsiString RetStr = "", LocationName = "";
7077  //record action time - may be arrival, departure or event for use later (added at v2.13.2)
7078  TDateTime ActionTime = Ptr->ArrivalTime;
7079  if(ActionTime == TDateTime(-1))
7080  {
7081  ActionTime = Ptr->DepartureTime;
7082  }
7083  if(ActionTime == TDateTime(-1))
7084  {
7085  ActionTime = Ptr->EventTime;
7086  }
7087  //If ActionTime still TDateTime(-1) then the train has terminated and 'None...' will be returned
7088  //Now correct it for repeats
7089  if(ActionTime != TDateTime(-1))
7090  {
7091  ActionTime = GetTrainTime(64, ActionTime);
7092  }
7093  if(int(DelayedRandMins) > 0)
7094  {
7095  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
7096  {
7097  throw Exception("Error - start entry in FloatingLabelNextString");
7098  }
7099  if(Ptr->FormatType == TimeTimeLoc)
7100  {
7101  if(TrainMode == Timetable)
7102  {
7103  if(!TrainAtLocation(0, LocationName) || (LocationName != Ptr->LocationName))
7104  // not arrived yet in tt mode
7105  {
7106  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(2, Ptr->ArrivalTime + TDateTime(DelayedRandMins/1440)));
7107  }
7108  else
7109  {
7110  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(3, Ptr->DepartureTime));
7111  }
7112  }
7113  else // TrainMode == Signaller
7114  {
7115  if(!DepartureTimeSet) // not arrived yet
7116  {
7117  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(45, Ptr->ArrivalTime + TDateTime(DelayedRandMins/1440)));
7118  }
7119  else
7120  {
7121  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(36, Ptr->DepartureTime));
7122  }
7123  }
7124  }
7125  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
7126  {
7127  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(4, Ptr->ArrivalTime + TDateTime(DelayedRandMins/1440)));
7128  }
7129  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
7130  {
7131  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(5, Ptr->DepartureTime));
7132  }
7133  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture) //added at v2.12.0 for becoming new service early (see BecomeNewservice)
7134  {
7135  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(46, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7136  }
7137  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
7138  {
7139  RetStr = "Pass " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(31, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7140  }
7141  else if(Ptr->Command == "Fns")
7142  {
7143  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(8, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " +
7144  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(6, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7145  RetStr = GetNewServiceDepartureInfo(0, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7146  }
7147  else if(Ptr->Command == "F-nshs")
7148  {
7149  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName + " at approx. " +
7150  Utilities->Format96HHMM(GetTrainTime(32, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7151  RetStr = GetNewServiceDepartureInfo(1, Ptr, 0, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7152  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
7153  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
7154  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
7155  }
7156  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7157  {
7158  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(9, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7159  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(7, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7160  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7161  RetStr = GetNewServiceDepartureInfo(2, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7162  }
7163  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7164  {
7165  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
7166  +" at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(8, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7167  RetStr = GetNewServiceDepartureInfo(3, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7168  }
7169  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7170  {
7171  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(10, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7172  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(9, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7173  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7174  RetStr = GetNewServiceDepartureInfo(4, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7175  }
7176  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7177  {
7178  RetStr ="None, train terminated at " + Ptr->LocationName;
7179  }
7180  else if(Ptr->Command == "Frh")
7181  {
7182  RetStr = "None, train terminated at " + Ptr->LocationName;
7183  }
7184  else if(Ptr->Command == "Fer")
7185  {
7186  AnsiString AllowedExits = "";
7187  RetStr = "Exit railway" + TrainController->GetExitLocationAndAt(1, Ptr->ExitList, AllowedExits) + " at approx. " + Utilities->Format96HHMM(GetTrainTime(10, Ptr->EventTime + TDateTime(DelayedRandMins/1440))) + AllowedExits;
7188  }
7189  else if(Ptr->Command == "Fjo")
7190  {
7191  RetStr = "Join " + TrainController->GetRepeatHeadCode(11, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName + " at approx. " +
7192  Utilities->Format96HHMM(GetTrainTime(11, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7193  }
7194  else if(Ptr->Command == "jbo")
7195  {
7196  RetStr = "Joined by " + TrainController->GetRepeatHeadCode(12, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7197  " at approx. " + Utilities->Format96HHMM(GetTrainTime(12, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7198  }
7199  else if(Ptr->Command == "fsp")
7200  {
7201  RetStr = "Front split to " + TrainController->GetRepeatHeadCode(13, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7202  " at approx. " + Utilities->Format96HHMM(GetTrainTime(13, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7203  }
7204  else if(Ptr->Command == "rsp")
7205  {
7206  RetStr = "Rear split to " + TrainController->GetRepeatHeadCode(14, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7207  " at approx. " + Utilities->Format96HHMM(GetTrainTime(14, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7208  }
7209  else if(Ptr->Command == "cdt")
7210  {
7211  RetStr = "Change direction at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(15, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7212  }
7213  else if(Ptr->Command == "dsc")
7214  {
7215  RetStr = "Change description at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(65, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7216  }
7217  }
7218  else if(TrainController->TTClockTime > ActionTime) //condition added at v2.13.2 for trains that are delayed other than suffering a random delay
7219  {
7220  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
7221  {
7222  throw Exception("Error - start entry in FloatingLabelNextString where TTClockTime > ActionTime");
7223  }
7224  if(Ptr->FormatType == TimeTimeLoc)
7225  {
7226  if(TrainMode == Timetable)
7227  {
7228  if(!TrainAtLocation(4, LocationName) || (LocationName != Ptr->LocationName))
7229  // not arrived yet in tt mode
7230  {
7231  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7232  }
7233  else
7234  {
7235  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);
7236  }
7237  }
7238  else // TrainMode == Signaller
7239  {
7240  if(!DepartureTimeSet) // not arrived yet
7241  {
7242  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7243  }
7244  else
7245  {
7246  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);
7247  }
7248  }
7249  }
7250  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
7251  {
7252  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7253  }
7254  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
7255  {
7256  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);
7257  }
7258  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture) //added at v2.12.0 for becoming new service early (see BecomeNewservice)
7259  {
7260  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7261  }
7262  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
7263  {
7264  RetStr = "Pass " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7265  }
7266  else if(Ptr->Command == "Fns")
7267  {
7268  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(60, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " +
7269  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7270  RetStr = GetNewServiceDepartureInfo(19, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7271  }
7272  else if(Ptr->Command == "F-nshs")
7273  {
7274  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName + " at approx. " +
7276  RetStr = GetNewServiceDepartureInfo(20, Ptr, 0, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7277  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
7278  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
7279  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
7280  }
7281  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7282  {
7283  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(61, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7284  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7285  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7286  RetStr = GetNewServiceDepartureInfo(21, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7287  }
7288  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7289  {
7290  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
7291  +" at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7292  RetStr = GetNewServiceDepartureInfo(22, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7293  }
7294  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7295  {
7296  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(62, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7297  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7298  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7299  RetStr = GetNewServiceDepartureInfo(23, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7300  }
7301  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7302  {
7303  RetStr ="None, train terminated at " + Ptr->LocationName;
7304  }
7305  else if(Ptr->Command == "Frh")
7306  {
7307  RetStr = "None, train terminated at " + Ptr->LocationName;
7308  }
7309  else if(Ptr->Command == "Fer")
7310  {
7311  AnsiString AllowedExits = "";
7312  RetStr = "Exit railway" + TrainController->GetExitLocationAndAt(5, Ptr->ExitList, AllowedExits) /*+ " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime)*/ + AllowedExits;
7313  }
7314  else if(Ptr->Command == "Fjo")
7315  {
7316  RetStr = "Join " + TrainController->GetRepeatHeadCode(63, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;// + " at approx. " +
7317 // Utilities->Format96HHMM(TrainController->TTClockTime);
7318  }
7319  else if(Ptr->Command == "jbo")
7320  {
7321  RetStr = "Joined by " + TrainController->GetRepeatHeadCode(64, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;// +
7322 // " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7323  }
7324  else if(Ptr->Command == "fsp")
7325  {
7326  RetStr = "Front split to " + TrainController->GetRepeatHeadCode(65, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7327  " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7328  }
7329  else if(Ptr->Command == "rsp")
7330  {
7331  RetStr = "Rear split to " + TrainController->GetRepeatHeadCode(66, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7332  " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7333  }
7334  else if(Ptr->Command == "cdt")
7335  {
7336  RetStr = "Change direction at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7337  }
7338  else if(Ptr->Command == "dsc")
7339  {
7340  RetStr = "Change description at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7341  }
7342  }
7343  else //train not delayed
7344  {
7345  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
7346  {
7347  throw Exception("Error - start entry in FloatingLabelNextString in final 'else'");
7348  }
7349  if(Ptr->FormatType == TimeTimeLoc)
7350  {
7351  if(TrainMode == Timetable)
7352  {
7353  if(!TrainAtLocation(3, LocationName) || (LocationName != Ptr->LocationName))
7354  // not arrived yet in tt mode
7355  {
7356  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(48, Ptr->ArrivalTime));
7357  }
7358  else
7359  {
7360  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(3, Ptr->DepartureTime));
7361  }
7362  }
7363  else // TrainMode == Signaller
7364  {
7365  if(!DepartureTimeSet) // not arrived yet
7366  {
7367  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(49, Ptr->ArrivalTime));
7368  }
7369  else
7370  {
7371  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(36, Ptr->DepartureTime));
7372  }
7373  }
7374  }
7375  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
7376  {
7377  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(50, Ptr->ArrivalTime));
7378  }
7379  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
7380  {
7381  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(5, Ptr->DepartureTime));
7382  }
7383  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture) //added at v2.12.0 for becoming new service early (see BecomeNewservice)
7384  {
7385  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(51, Ptr->EventTime));
7386  }
7387  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
7388  {
7389  RetStr = "Pass " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(52, Ptr->EventTime));
7390  }
7391  else if(Ptr->Command == "Fns")
7392  {
7393  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(53, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " +
7394  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(53, Ptr->EventTime));
7395  RetStr = GetNewServiceDepartureInfo(10, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7396  }
7397  else if(Ptr->Command == "F-nshs")
7398  {
7399  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName + " at approx. " +
7401  RetStr = GetNewServiceDepartureInfo(12, Ptr, 0, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7402  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
7403  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
7404  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
7405  }
7406  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7407  {
7408  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(54, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7409  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(55, Ptr->EventTime));
7410  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7411  RetStr = GetNewServiceDepartureInfo(14, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7412  }
7413  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7414  {
7415  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
7416  +" at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(56, Ptr->EventTime));
7417  RetStr = GetNewServiceDepartureInfo(16, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7418  }
7419  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7420  {
7421  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(55, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7422  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(57, Ptr->EventTime));
7423  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7424  RetStr = GetNewServiceDepartureInfo(18, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7425  }
7426  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7427  {
7428  RetStr ="None, train terminated at " + Ptr->LocationName;
7429  }
7430  else if(Ptr->Command == "Frh")
7431  {
7432  RetStr = "None, train terminated at " + Ptr->LocationName;
7433  }
7434  else if(Ptr->Command == "Fer")
7435  {
7436  AnsiString AllowedExits = "";
7437  RetStr = "Exit railway" + TrainController->GetExitLocationAndAt(4, Ptr->ExitList, AllowedExits) /*+ " at approx. " + Utilities->Format96HHMM(GetTrainTime(62, Ptr->EventTime))*/ + AllowedExits;
7438  }
7439  else if(Ptr->Command == "Fjo")
7440  {
7441  RetStr = "Join " + TrainController->GetRepeatHeadCode(56, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;// + " at approx. " +
7442 // Utilities->Format96HHMM(GetTrainTime(58, Ptr->EventTime));
7443  }
7444  else if(Ptr->Command == "jbo")
7445  {
7446  RetStr = "Joined by " + TrainController->GetRepeatHeadCode(57, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;// +
7447 // " at approx. " + Utilities->Format96HHMM(GetTrainTime(59, Ptr->EventTime));
7448  }
7449  else if(Ptr->Command == "fsp")
7450  {
7451  RetStr = "Front split to " + TrainController->GetRepeatHeadCode(58, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7452  " at approx. " + Utilities->Format96HHMM(GetTrainTime(60, Ptr->EventTime));
7453  }
7454  else if(Ptr->Command == "rsp")
7455  {
7456  RetStr = "Rear split to " + TrainController->GetRepeatHeadCode(59, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7457  " at approx. " + Utilities->Format96HHMM(GetTrainTime(61, Ptr->EventTime));
7458  }
7459  else if(Ptr->Command == "cdt")
7460  {
7461  RetStr = "Change direction at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(63, Ptr->EventTime));
7462  }
7463  else if(Ptr->Command == "dsc")
7464  {
7465  RetStr = "Change description at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(66, Ptr->EventTime));
7466  }
7467  }
7468  Utilities->CallLogPop(1124);
7469  return(RetStr);
7470 }
7471 
7472 // ---------------------------------------------------------------------------
7473 /* as was
7474 AnsiString TTrain::FloatingLabelNextString(int Caller, TActionVectorEntry *Ptr)
7475 {
7476  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - &TrainDataEntryPtr->ActionVector.front()) +
7477  ",FloatingLabelNextString" + "," + HeadCode);
7478  AnsiString RetStr = "", LocationName = "";
7479 
7480  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
7481  {
7482  throw Exception("Error - start entry in FloatingLabelNextString");
7483  }
7484  if(Ptr->FormatType == TimeTimeLoc)
7485  {
7486  if(TrainMode == Timetable)
7487  {
7488  if(!TrainAtLocation(, LocationName) || (LocationName != Ptr->LocationName))
7489  // not arrived yet in tt mode
7490  {
7491  RetStr = "Arrive " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->ArrivalTime));
7492  }
7493  else
7494  {
7495  RetStr = "Depart " + Ptr->LocationName + " approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(, Ptr->DepartureTime));
7496  }
7497  }
7498  else // TrainMode == Signaller
7499  {
7500  if(!DepartureTimeSet) // not arrived yet
7501  {
7502  RetStr = "Arrive " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->ArrivalTime));
7503  }
7504  else
7505  {
7506  RetStr = "Depart " + Ptr->LocationName + " approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(, Ptr->DepartureTime));
7507  }
7508  }
7509  }
7510  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
7511  {
7512  RetStr = "Arrive " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->ArrivalTime));
7513  }
7514  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
7515  {
7516  RetStr = "Depart " + Ptr->LocationName + " approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(, Ptr->DepartureTime));
7517  }
7518  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture) //added at v2.12.0 for becoming new service early (see BecomeNewservice)
7519  {
7520  RetStr = "Depart " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7521  }
7522  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
7523  {
7524  RetStr = "Pass " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7525  }
7526  else if(Ptr->Command == "Fns")
7527  {
7528  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " +
7529  Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7530  RetStr = GetNewServiceDepartureInfo(, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7531  }
7532  else if(Ptr->Command == "F-nshs")
7533  {
7534  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName + " at " +
7535  Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7536  RetStr = GetNewServiceDepartureInfo(, Ptr, 0, Ptr->LinkedTrainEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7537  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
7538  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
7539  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
7540  }
7541  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7542  {
7543  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7544  Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7545  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7546  RetStr = GetNewServiceDepartureInfo(, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7547  }
7548  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7549  {
7550  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
7551  +" at " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7552  RetStr = GetNewServiceDepartureInfo(, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7553  }
7554  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7555  {
7556  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7557  Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7558  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7559  RetStr = GetNewServiceDepartureInfo(, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7560  }
7561  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7562  {
7563  RetStr ="None, train terminated at " + Ptr->LocationName;
7564  }
7565  else if(Ptr->Command == "Frh")
7566  {
7567  RetStr = "None, train terminated at " + Ptr->LocationName;
7568  }
7569  else if(Ptr->Command == "Fer")
7570  {
7571  AnsiString AllowedExits = "";
7572  RetStr = "Exit railway" + TrainController->GetExitLocationAndAt(, Ptr->ExitList, AllowedExits) + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime)) + AllowedExits;
7573  }
7574  else if(Ptr->Command == "Fjo")
7575  {
7576  RetStr = "Join " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName + " at " +
7577  Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7578  }
7579  else if(Ptr->Command == "jbo")
7580  {
7581  RetStr = "Joined by " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7582  " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7583  }
7584  else if(Ptr->Command == "fsp")
7585  {
7586  RetStr = "Front split to " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7587  " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7588  }
7589  else if(Ptr->Command == "rsp")
7590  {
7591  RetStr = "Rear split to " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7592  " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7593  }
7594  else if(Ptr->Command == "cdt")
7595  {
7596  RetStr = "Change direction at " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7597  }
7598  Utilities->CallLogPop();
7599  return(RetStr);
7600 }
7601 */
7602 // ---------------------------------------------------------------------------
7603 
7604 AnsiString TTrain::GetNewServiceDepartureInfo(int Caller, TActionVectorEntry *Ptr, int RptNum, TTrainDataEntry *LinkedTrainDataPtr, AnsiString RetStr, bool TimetableTime)
7605 { //last bool added at v2.13.2 so departure info adds random delay if actual rather than not timetable time required
7606  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - &TrainDataEntryPtr->ActionVector.front()) + ","
7607  + AnsiString(RptNum) + ",GetNewServiceDepartureInfo," + HeadCode);
7608  AnsiString DepTime = "", EventTime = "";
7609  bool CDTFlag = false; //reports if train changes direction before departs
7610  TActionVector NewServiceAV = LinkedTrainDataPtr->ActionVector;
7611  AnsiString CurrentLocation = NewServiceAV.at(0).LocationName; //added at v2.12.0 to show departure direction
7612  AnsiString TowardsLocation = ""; //added at v2.12.0 to show departure direction
7613  for(TActionVectorIterator AVI = NewServiceAV.begin(); AVI < NewServiceAV.end(); AVI++) //added at v2.12.0 to obtain departure direction
7614  {
7615  if((AVI->LocationName != CurrentLocation) && (AVI->LocationName != "") && (TowardsLocation == ""))
7616  {
7617  TowardsLocation = AVI->LocationName;
7618  }
7619  else if((AVI->Command == "Fer") && (TowardsLocation == "") && !AVI->ExitList.empty())
7620  {
7621  TTrackElement TE = Track->TrackElementAt(1452, (AVI->ExitList.front()));
7622  if(TE.ActiveTrackElementName != "")
7623  {
7624  TowardsLocation = TE.ActiveTrackElementName;
7625  }
7626  else
7627  {
7628  TowardsLocation = AnsiString("track element ID ") + TE.ElementID;
7629  }
7630  }
7631  }
7632 
7633  for(TActionVectorIterator AVI = NewServiceAV.begin(); AVI < NewServiceAV.end(); AVI++)
7634  {
7635  if(AVI->Command == "cdt")
7636  {
7637  CDTFlag = !CDTFlag; //toggles flag - allows for there being more than one cdt before departure
7638  continue;
7639  }
7640  if((AVI->Command == "fsp") || (AVI->Command == "rsp"))
7641  {
7642  TDateTime TTTime = TrainController->GetControllerTrainTime(19, AVI->EventTime, RptNum, IncrementalMinutes);
7643  if((DelayedRandMins >= 1) && !TimetableTime)
7644  {
7645  EventTime = Utilities->Format96HHMM(TTTime + TDateTime(DelayedRandMins/1440));
7646  }
7647  else if((TrainController->TTClockTime > TTTime) && !TimetableTime)
7648  {
7650  }
7651  else //((DelayedRandMins == 0) && (TTClockTime <= TTTime)) || TimetableTime
7652  {
7653  EventTime = Utilities->Format96HHMM(TTTime);
7654  }
7655  RetStr += "\nNew service splits at approx. " + EventTime;
7656  Utilities->CallLogPop(2234);
7657  return(RetStr);
7658  }
7659  if(AVI->Command == "jbo") //added at v2.15.0
7660  {
7661  TDateTime TTTime = TrainController->GetControllerTrainTime(28, AVI->EventTime, RptNum, IncrementalMinutes);
7662  if((DelayedRandMins >= 1) && !TimetableTime)
7663  {
7664  EventTime = Utilities->Format96HHMM(TTTime + TDateTime(DelayedRandMins/1440));
7665  }
7666  else if((TrainController->TTClockTime > TTTime) && !TimetableTime)
7667  {
7669  }
7670  else //((DelayedRandMins == 0) && (TTClockTime <= TTTime)) || TimetableTime
7671  {
7672  EventTime = Utilities->Format96HHMM(TTTime);
7673  }
7674  RetStr += "\nNew service joined by " + TrainController->GetRepeatHeadCode(68, AVI->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at approx. " + EventTime;
7675  Utilities->CallLogPop(2595); //above added GetRepeatHeadCode at v2.18.0 (was just AVI->OtherHeadCode before)
7676  return(RetStr);
7677  }
7678  if((AVI->Command == "Fns") || (AVI->Command == "F-nshs") || (AVI->Command == "Fns-sh")) //added at v2.15.0
7679  {
7680  TDateTime TTTime = TrainController->GetControllerTrainTime(29, AVI->EventTime, RptNum, IncrementalMinutes);
7681  if((DelayedRandMins >= 1) && !TimetableTime)
7682  {
7683  EventTime = Utilities->Format96HHMM(TTTime + TDateTime(DelayedRandMins/1440));
7684  }
7685  else if((TrainController->TTClockTime > TTTime) && !TimetableTime)
7686  {
7688  }
7689  else //((DelayedRandMins == 0) && (TTClockTime <= TTTime)) || TimetableTime
7690  {
7691  EventTime = Utilities->Format96HHMM(TTTime);
7692  }
7693  RetStr += "\nNew service finishes and forms another new service at approx. " + EventTime;
7694  Utilities->CallLogPop(2615);
7695  return(RetStr);
7696  }
7697  if(AVI->Command == "Fjo") //added at v2.15.0
7698  {
7699  TDateTime TTTime = TrainController->GetControllerTrainTime(26, AVI->EventTime, RptNum, IncrementalMinutes);
7700  if((DelayedRandMins >= 1) && !TimetableTime)
7701  {
7702  EventTime = Utilities->Format96HHMM(TTTime + TDateTime(DelayedRandMins/1440));
7703  }
7704  else if((TrainController->TTClockTime > TTTime) && !TimetableTime)
7705  {
7707  }
7708  else //((DelayedRandMins == 0) && (TTClockTime <= TTTime)) || TimetableTime
7709  {
7710  EventTime = Utilities->Format96HHMM(TTTime);
7711  }
7712  RetStr += "\nNew service finishes and joins " + TrainController->GetRepeatHeadCode(69, AVI->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at approx. " + EventTime;
7713  Utilities->CallLogPop(2605); //above added GetRepeatHeadCode at v2.18.0 (was just AVI->OtherHeadCode before)
7714  return(RetStr);
7715  }
7716  if(AVI->Command == "Frh") //added at v2.15.0
7717  {
7718  RetStr += "\nNew service finishes and remains at the location.";
7719  Utilities->CallLogPop(2606);
7720  return(RetStr);
7721  }
7722  if((AVI->FormatType == TimeLoc) && (AVI->DepartureTime > TDateTime(-1))) //departure time set
7723  {
7724  if(TimetableTime) //don't add random delay
7725  {
7726  DepTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(17, AVI->DepartureTime, RptNum, IncrementalMinutes));
7727  if(CDTFlag)
7728  {
7729  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
7730  {
7731  RetStr += "\nNew service changes direction then departs towards " + TowardsLocation + " at " + DepTime;
7732  }
7733  else
7734  {
7735  RetStr += "\nNew service changes direction then departs at " + DepTime;
7736  }
7737  }
7738  else
7739  {
7740  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
7741  {
7742  RetStr += "\nNew service departs towards " + TowardsLocation + " at " + DepTime;
7743  }
7744  else
7745  {
7746  RetStr += "\nNew service departs at " + DepTime;
7747  }
7748  }
7749  }
7750  else if(DelayedRandMins >= 1)//add random delay
7751  {
7752  DepTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(24, AVI->DepartureTime + TDateTime(DelayedRandMins/1440), RptNum, IncrementalMinutes));
7753  if(CDTFlag)
7754  {
7755  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
7756  {
7757  RetStr += "\nNew service changes direction then departs towards " + TowardsLocation + " at approx. " + DepTime;
7758  }
7759  else
7760  {
7761  RetStr += "\nNew service changes direction then departs at approx. " + DepTime;
7762  }
7763  }
7764  else
7765  {
7766  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
7767  {
7768  RetStr += "\nNew service departs towards " + TowardsLocation + " at approx. " + DepTime;
7769  }
7770  else
7771  {
7772  RetStr += "\nNew service departs at approx. " + DepTime;
7773  }
7774  }
7775  }
7776  else //no random delay but may be delayed for other reasons
7777  {
7778  TDateTime TTTime = TrainController->GetControllerTrainTime(25, AVI->DepartureTime, RptNum, IncrementalMinutes);
7779  if(TrainController->TTClockTime > TTTime)
7780  {
7782  }
7783  else
7784  {
7785  DepTime = Utilities->Format96HHMM(TTTime);
7786  }
7787  if(CDTFlag)
7788  {
7789  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
7790  {
7791  RetStr += "\nNew service changes direction then departs towards " + TowardsLocation + " at approx. " + DepTime;
7792  }
7793  else
7794  {
7795  RetStr += "\nNew service changes direction then departs at approx. " + DepTime;
7796  }
7797  }
7798  else
7799  {
7800  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
7801  {
7802  RetStr += "\nNew service departs towards " + TowardsLocation + " at approx. " + DepTime;
7803  }
7804  else
7805  {
7806  RetStr += "\nNew service departs at approx. " + DepTime;
7807  }
7808  }
7809  }
7810  Utilities->CallLogPop(2236);
7811  return(RetStr);
7812  }
7813  }
7814  Utilities->CallLogPop(2208);
7815  return(RetStr); //if reach here then RetStr doesn't change
7816 }
7817 
7818 // ---------------------------------------------------------------------------
7819 
7821 // Enter with Ptr pointing to first action to be listed (i.e. next action)
7822 // If there are actions to be skipped but a departure is awaited (SkippedDeparture = true) then after the departure Ptr moves forward by SkipPtrValue
7823 {
7824  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - &TrainDataEntryPtr->ActionVector.front()) +
7825  ",FloatingTimetableString" + "," + HeadCode);
7826  AnsiString RetStr = "", PartStr = "";
7827  int Count = 0;
7828  bool SkipDep = false, SkipDepActedOn = false; //SkipDepActedOn ensures only one SkipDep acted on
7829  AnsiString LocName = Ptr->LocationName;
7830 
7831  if((Ptr->Command != "") && (Ptr->Command[1] == 'S') && (TrainMode == Timetable))
7832  // can start in signaller control so exclude this
7833  {
7834  throw Exception("Error - start entry in FloatingTimetableString");
7835  }
7836  TActionVectorEntry *EntryPtr = Ptr; //used in TimeTimeLoc check later
7837  bool FirstPass = true;
7838  Ptr--; // because incremented at start of loop
7839 
7840  // different first TimeTimeLoc display if in signaller control
7841  do
7842  {
7843  Ptr++;
7844  AnsiString TrainLoc = ""; //moved to here from below at v2.20.1 when added last 2 conditions in first PassTime check below (see Albie Vowles' email of 25/07/24)
7845  if((Ptr->FormatType == Repeat) || TimetableFinished)
7846  {
7847  break;
7848  }
7849  if((Ptr->FormatType == TimeTimeLoc) && FirstPass)
7850  {
7851 // AnsiString TrainLoc = ""; moved from here at v2.20.1 - see above
7852  if(TrainMode == Timetable)
7853  {
7854  if(TrainAtLocation(1, TrainLoc) && (TrainLoc == Ptr->LocationName) && (Ptr == EntryPtr)) //added '&& (Ptr == EntryPtr)' at v2.6.0 when allow multiple same location entries
7855  {
7856  PartStr = Utilities->Format96HHMM(GetTrainTime(33, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
7857  if((LocName == Ptr->LocationName) && (LocName != "") && SkippedDeparture && !SkipDepActedOn)
7858  {
7859  SkipDep = true; //0 for incremental minutes because don't reduce the departure time when later actions have been skipped
7860  }
7861  }
7862  else if(Ptr->ArrivalTime == Ptr->DepartureTime)
7863  {
7864  PartStr = Utilities->Format96HHMM(GetTrainTime(34, Ptr->ArrivalTime)) + ": Arrive & depart from " + Ptr->LocationName;
7865  }
7866  else
7867  {
7868  PartStr = Utilities->Format96HHMM(GetTrainTime(16, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName + '\n' +
7869  Utilities->Format96HHMM(GetTrainTime(17, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
7870  Count++; // because there are 2 entries
7871  }
7872  }
7873  else // TrainMode == Signaller
7874  {
7875  if(DepartureTimeSet)
7876  {
7877  PartStr = Utilities->Format96HHMM(GetTrainTime(37, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
7878  if((LocName == Ptr->LocationName) && (LocName != "") && SkippedDeparture && !SkipDepActedOn)
7879  {
7880  SkipDep = true; //0 for incremental minutes because don't reduce the departure time when later actions have been skipped
7881  }
7882  }
7883  else if(Ptr->ArrivalTime == Ptr->DepartureTime)
7884  {
7885  PartStr = Utilities->Format96HHMM(GetTrainTime(38, Ptr->ArrivalTime)) + ": Arrive & depart from " + Ptr->LocationName;
7886  }
7887  else
7888  {
7889  PartStr = Utilities->Format96HHMM(GetTrainTime(39, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName + '\n' +
7890  Utilities->Format96HHMM(GetTrainTime(40, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
7891  Count++; // because there are 2 entries
7892  }
7893  }
7894  }
7895  else if((Ptr->FormatType == TimeTimeLoc) && !FirstPass)
7896  {
7897  AnsiString TrainLoc = "";
7898  if((TrainAtLocation(2, TrainLoc)) && (TrainLoc == Ptr->LocationName) && (Ptr == EntryPtr)) //added '&& (Ptr == EntryPtr)' at v2.6.0 when allow multiple same location entries
7899  {
7900  PartStr = Utilities->Format96HHMM(GetTrainTime(41, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
7901  if((LocName == Ptr->LocationName) && (LocName != "") && SkippedDeparture && !SkipDepActedOn)
7902  {
7903  SkipDep = true; //0 for incremental minutes because don't reduce the departure time when later actions have been skipped
7904  }
7905  }
7906  else if(Ptr->ArrivalTime == Ptr->DepartureTime)
7907  {
7908  PartStr = Utilities->Format96HHMM(GetTrainTime(42, Ptr->ArrivalTime)) + ": Arrive & depart from " + Ptr->LocationName;
7909  }
7910  else
7911  {
7912  PartStr = Utilities->Format96HHMM(GetTrainTime(43, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName + '\n' +
7913  Utilities->Format96HHMM(GetTrainTime(44, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
7914  Count++; // because there are 2 entries
7915  }
7916  }
7917  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
7918  {
7919  PartStr = Utilities->Format96HHMM(GetTrainTime(18, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName;
7920  }
7921  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
7922  {
7923  PartStr = Utilities->Format96HHMM(GetTrainTime(19, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
7924  if((LocName == Ptr->LocationName) && (LocName != "") && SkippedDeparture && !SkipDepActedOn)
7925  {
7926  SkipDep = true; //0 for incremental minutes because don't reduce the departure time when later actions have been skipped
7927  }
7928  }
7929  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture && (TrainAtLocation(5, TrainLoc)) && (TrainLoc == Ptr->LocationName)) //added at v2.12.0 for becoming new service early (see BecomeNewService)
7930  { //note that TreatPassAsTimeLocDeparture can't be set if have SkippedDeparture
7931  //added last 2 condits at v2.20.1 as otherwise all later passes listed as departures (see Albie Vowles' email of 25/07/24)
7932  PartStr = Utilities->Format96HHMM(GetTrainTime(47, Ptr->EventTime)) + ": Depart from " + Ptr->LocationName;
7933  }
7934  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
7935  {
7936  PartStr = Utilities->Format96HHMM(GetTrainTime(30, Ptr->EventTime)) + ": Pass " + Ptr->LocationName;
7937  }
7938  else if(Ptr->Command == "Fns")
7939  {
7940  PartStr = Utilities->Format96HHMM(GetTrainTime(20, Ptr->EventTime)) + ": Form new service " + TrainController->GetRepeatHeadCode(15,
7941  Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
7942  PartStr = GetNewServiceDepartureInfo(5, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to PartStr
7943  }
7944  else if(Ptr->Command == "F-nshs")
7945  {
7946  PartStr = Utilities->Format96HHMM(GetTrainTime(35, Ptr->EventTime)) + ": Form new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " +
7947  Ptr->LocationName;
7948  PartStr = GetNewServiceDepartureInfo(6, Ptr, 0, Ptr->LinkedTrainEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to RetStr
7949  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
7950  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
7951  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
7952  }
7953  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not the last repeat number
7954  {
7955  PartStr = Utilities->Format96HHMM(GetTrainTime(21, Ptr->EventTime)) + ": Form new service " + TrainController->GetRepeatHeadCode(16,
7956  Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " + Ptr->LocationName;
7957  // use RepeatNumber+1 because it's the repeat number of the NEXT shuttle service that is relevant
7958  PartStr = GetNewServiceDepartureInfo(7, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to RetStr
7959  }
7960  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7961  {
7962  PartStr = Utilities->Format96HHMM(GetTrainTime(22, Ptr->EventTime)) + ": Form new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
7963  +" at " + Ptr->LocationName;
7964  PartStr = GetNewServiceDepartureInfo(8, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to RetStr
7965  }
7966  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not the last repeat number
7967  {
7968  PartStr = Utilities->Format96HHMM(GetTrainTime(23, Ptr->EventTime)) + ": Form new service " + TrainController->GetRepeatHeadCode(17,
7969  Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " + Ptr->LocationName;
7970  // use RepeatNumber+1 because it's the repeat number of the NEXT shuttle service that is relevant
7971  PartStr = GetNewServiceDepartureInfo(9, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to RetStr
7972  }
7973  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7974  {
7975  PartStr = "Terminate at " + Ptr->LocationName;
7976  }
7977  else if(Ptr->Command == "Frh")
7978  {
7979  PartStr = "Terminate at " + Ptr->LocationName;
7980  }
7981  else if(Ptr->Command == "Fer")
7982  {
7983  AnsiString AllowedExits = "";
7984  PartStr = Utilities->Format96HHMM(GetTrainTime(24, Ptr->EventTime)) + ": Exit railway" + TrainController->GetExitLocationAndAt(2, Ptr->ExitList, AllowedExits) + AllowedExits;
7985  }
7986  else if(Ptr->Command == "Fjo")
7987  {
7988  PartStr = Utilities->Format96HHMM(GetTrainTime(25, Ptr->EventTime)) + ": Join " + TrainController->GetRepeatHeadCode(18, Ptr->OtherHeadCode,
7989  RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
7990  }
7991  else if(Ptr->Command == "jbo")
7992  {
7993  PartStr = Utilities->Format96HHMM(GetTrainTime(26, Ptr->EventTime)) + ": Joined by " + TrainController->GetRepeatHeadCode(19, Ptr->OtherHeadCode,
7994  RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
7995  }
7996  else if(Ptr->Command == "fsp")
7997  {
7998  PartStr = Utilities->Format96HHMM(GetTrainTime(27, Ptr->EventTime)) + ": Front split to " + TrainController->GetRepeatHeadCode(20,
7999  Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
8000  if(Ptr->SplitDistribution != "")
8001  {
8002  PartStr+= ", split mass%-Power% = " + Ptr->SplitDistribution;
8003  }
8004  else
8005  {
8006  PartStr+= ", split mass%-Power% = 50-50";
8007  }
8008  }
8009  else if(Ptr->Command == "rsp")
8010  {
8011  PartStr = Utilities->Format96HHMM(GetTrainTime(28, Ptr->EventTime)) + ": Rear split to " + TrainController->GetRepeatHeadCode(21,
8012  Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
8013  if(Ptr->SplitDistribution != "")
8014  {
8015  PartStr+= ", split mass%-Power% = " + Ptr->SplitDistribution;
8016  }
8017  else
8018  {
8019  PartStr+= ", split mass%-Power% = 50-50";
8020  }
8021  }
8022  else if(Ptr->Command == "cdt")
8023  {
8024  PartStr = Utilities->Format96HHMM(GetTrainTime(29, Ptr->EventTime)) + ": Change direction at " + Ptr->LocationName;
8025  }
8026  else if(Ptr->Command == "dsc")
8027  {
8028  PartStr = Utilities->Format96HHMM(GetTrainTime(67, Ptr->EventTime)) + ": Change description at " + Ptr->LocationName;
8029  }
8030  if(RetStr != "")
8031  {
8032  RetStr = RetStr + '\n' + PartStr;
8033  }
8034  else
8035  {
8036  RetStr = PartStr;
8037  }
8038  FirstPass = false;
8039  Count++;
8040 
8041  if(SkipDep)
8042  {
8043  Ptr = &(TrainDataEntryPtr->ActionVector.at(0)) + SkipPtrValue;
8044  Ptr--; //it is incremented at the start of the next loop
8045  SkipDep = false;
8046  SkipDepActedOn = true;
8047  }
8048  }
8049  while(!TimetableFinished && (Count < 32) && ((Ptr->Command == "") || ((Ptr->Command != "") && (Ptr->Command[1] != 'F'))));
8050  // limit of 32 allows a max of 34 entries (33 + 1 for the new service departure time) (may have gone from 32 to 34 because of a TimeTimeLoc), which with track and
8051  // train status gives a max of 48 lines, at 13 pixels each, = 624 pixels & screen height has 641 so will fit comfortably. Also 34 timetable entries is as far
8052  // forward as anyone should wish to see without looking at the full timetable
8053  if(TimetableFinished)
8054  {
8055  if(TrainMode == Timetable)
8056  {
8057  RetStr = "Timetable finished";
8058  }
8059  else
8060  {
8061  RetStr = "No timetable";
8062  }
8063  }
8064  Utilities->CallLogPop(1125);
8065  return("Timetable:\n" + RetStr);
8066 }
8067 
8068 // ---------------------------------------------------------------------------
8069 
8070 void TTrain::SaveOneSessionTrain(int Caller, std::ofstream &OutFile)
8071 {
8072  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveOneSessionTrain" + "," + HeadCode);
8073  Utilities->SaveFileString(OutFile, HeadCode);
8076  Utilities->SaveFileInt(OutFile, StartSpeed);
8079  Utilities->SaveFileInt(OutFile, RepeatNumber);
8082  Utilities->SaveFileInt(OutFile, Mass);
8085  Utilities->SaveFileDouble(OutFile, EntrySpeed);
8092  Utilities->SaveFileDouble(OutFile, BrakeRate);
8096  Utilities->SaveFileDouble(OutFile, double(EntryTime));
8097  Utilities->SaveFileDouble(OutFile, double(ExitTimeHalf));
8098  Utilities->SaveFileDouble(OutFile, double(ExitTimeFull));
8099  Utilities->SaveFileDouble(OutFile, double(ReleaseTime));
8100  Utilities->SaveFileDouble(OutFile, double(TRSTime));
8101  Utilities->SaveFileDouble(OutFile, double(LastActionTime));
8105  Utilities->SaveFileInt(OutFile, (short)TrainMode);
8110  Utilities->SaveFileBool(OutFile, Derailed);
8112  Utilities->SaveFileBool(OutFile, Crashed);
8119  Utilities->SaveFileBool(OutFile, NotInService);
8120  Utilities->SaveFileBool(OutFile, Plotted);
8121  Utilities->SaveFileBool(OutFile, TrainGone);
8122  Utilities->SaveFileBool(OutFile, SPADFlag);
8124  Utilities->SaveFileInt(OutFile, HOffset[0]);
8125  Utilities->SaveFileInt(OutFile, HOffset[1]);
8126  Utilities->SaveFileInt(OutFile, HOffset[2]);
8127  Utilities->SaveFileInt(OutFile, HOffset[3]);
8128  Utilities->SaveFileInt(OutFile, VOffset[0]);
8129  Utilities->SaveFileInt(OutFile, VOffset[1]);
8130  Utilities->SaveFileInt(OutFile, VOffset[2]);
8131  Utilities->SaveFileInt(OutFile, VOffset[3]);
8132  Utilities->SaveFileInt(OutFile, PlotElement[0]);
8133  Utilities->SaveFileInt(OutFile, PlotElement[1]);
8134  Utilities->SaveFileInt(OutFile, PlotElement[2]);
8135  Utilities->SaveFileInt(OutFile, PlotElement[3]);
8136  Utilities->SaveFileInt(OutFile, PlotEntryPos[0]);
8137  Utilities->SaveFileInt(OutFile, PlotEntryPos[1]);
8138  Utilities->SaveFileInt(OutFile, PlotEntryPos[2]);
8139  Utilities->SaveFileInt(OutFile, PlotEntryPos[3]);
8141  Utilities->SaveFileInt(OutFile, (short)Straddle);
8142  Utilities->SaveFileInt(OutFile, NextTrainID);
8143  Utilities->SaveFileInt(OutFile, TrainID);
8144  Utilities->SaveFileInt(OutFile, LeadElement);
8145  Utilities->SaveFileInt(OutFile, LeadEntryPos);
8146  Utilities->SaveFileInt(OutFile, LeadExitPos);
8147  Utilities->SaveFileInt(OutFile, MidElement);
8148  Utilities->SaveFileInt(OutFile, MidEntryPos);
8149  Utilities->SaveFileInt(OutFile, MidExitPos);
8150  Utilities->SaveFileInt(OutFile, LagElement);
8151  Utilities->SaveFileInt(OutFile, LagEntryPos);
8152  Utilities->SaveFileInt(OutFile, LagExitPos);
8153  int ColourNumber;
8154 
8156  {
8157  ColourNumber = 0;
8158  }
8160  {
8161  ColourNumber = 1;
8162  }
8164  {
8165  ColourNumber = 2;
8166  }
8168  {
8169  ColourNumber = 3;
8170  }
8172  {
8173  ColourNumber = 4;
8174  }
8176  {
8177  ColourNumber = 5;
8178  }
8180  {
8181  ColourNumber = 6;
8182  }
8184  {
8185  ColourNumber = 7;
8186  }
8188  {
8189  ColourNumber = 8;
8190  }
8192  {
8193  ColourNumber = 9;
8194  }
8196  {
8197  ColourNumber = 10;
8198  }
8200  {
8201  ColourNumber = 11;
8202  }
8204  {
8205  ColourNumber = 12;
8206  }
8207  else if(BackgroundColour == clTRSBackground)
8208  {
8209  ColourNumber = 13;
8210  }
8212  {
8213  ColourNumber = 14; // added at v2.4.0
8214  }
8215  Utilities->SaveFileInt(OutFile, ColourNumber);
8216 
8217  // additional data
8218  bool ForwardHeadCode;
8219 
8220  if(HeadCodePosition[3] == HeadCodeGrPtr[3])
8221  {
8222  ForwardHeadCode = true;
8223  }
8224  // can't use 'if(HeadCodePosition[0] == HeadCodeGrPtr[0])' as HeadCodePosition[0] is set to FrontCodePtr
8225  else
8226  {
8227  ForwardHeadCode = false;
8228  }
8229  Utilities->SaveFileBool(OutFile, ForwardHeadCode);
8230 
8231  int TrainDataEntryValue = TrainDataEntryPtr - &(TrainController->TrainDataVector.at(0));
8232 
8233  Utilities->SaveFileInt(OutFile, TrainDataEntryValue);
8234  int ActionVectorEntryValue = ActionVectorEntryPtr - &(TrainDataEntryPtr->ActionVector.at(0));
8235 
8236  Utilities->SaveFileInt(OutFile, ActionVectorEntryValue);
8237  // now the marker comes next which was ****** originally but used for RestoreTimetableLocation as well some time ago (came before the asterisks)
8238  // but at v2.4.0 need to include StoppedWithoutPower, while keeping length of marker at 6, because that is tested in earlier versions
8239  // so use the last asterisk position for this - 0 for false & 1 for true
8240  // note that failed train data is handled in InterfaceUnit.cpp & stored after the performance file
8241  AnsiString Marker;
8242 
8244  {
8245  Marker = "*****1";
8246  }
8247  else
8248  {
8249  Marker = "*****0";
8250  }
8251  if(RestoreTimetableLocation == "")
8252  {
8253  Utilities->SaveFileString(OutFile, Marker);
8254  }
8255  else
8256  {
8257  AnsiString CombinedString = RestoreTimetableLocation + Marker;
8258  Utilities->SaveFileString(OutFile, CombinedString);
8259  // RestoreTimetableLocation + marker
8260  }
8261  // Note: including RestoreTimetableLocation with the marker is to correct an oversight - it should have been saved earlier
8262  Utilities->CallLogPop(1457);
8263 }
8264 
8265 // ---------------------------------------------------------------------------
8266 
8267 void TTrain::LoadOneSessionTrain(int Caller, std::ifstream &InFile)
8268 {
8269  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LoadOneSessionTrain"); // don't have headcode yet
8270  HeadCode = Utilities->LoadFileString(InFile);
8273  StartSpeed = Utilities->LoadFileInt(InFile);
8275  if(SignallerMaxSpeed < 10)
8276  {
8277  SignallerMaxSpeed = 10; // added at v0.6 to avoid low max speeds
8278  }
8280  RepeatNumber = Utilities->LoadFileInt(InFile);
8283  Mass = Utilities->LoadFileInt(InFile);
8286  {
8288  }
8289  // above added at v2.1.0 for legacy session files where value may not have been limited
8291  EntrySpeed = Utilities->LoadFileDouble(InFile);
8295  if(TimetableMaxRunningSpeed < 10)
8296  {
8297  TimetableMaxRunningSpeed = 10; // added at v0.6 to avoid low max speeds
8298  }
8300  if(MaxRunningSpeed < 10)
8301  {
8302  MaxRunningSpeed = 10; // added at v0.6 to avoid low max speeds
8303  }
8306  BrakeRate = Utilities->LoadFileDouble(InFile);
8310  EntryTime = TDateTime(Utilities->LoadFileDouble(InFile));
8311  ExitTimeHalf = TDateTime(Utilities->LoadFileDouble(InFile));
8312  ExitTimeFull = TDateTime(Utilities->LoadFileDouble(InFile));
8313  ReleaseTime = TDateTime(Utilities->LoadFileDouble(InFile));
8314  TRSTime = TDateTime(Utilities->LoadFileDouble(InFile));
8315  LastActionTime = TDateTime(Utilities->LoadFileDouble(InFile));
8324  Derailed = Utilities->LoadFileBool(InFile);
8326  Crashed = Utilities->LoadFileBool(InFile);
8333  NotInService = Utilities->LoadFileBool(InFile);
8334  Plotted = Utilities->LoadFileBool(InFile);
8335  TrainGone = Utilities->LoadFileBool(InFile);
8336  SPADFlag = Utilities->LoadFileBool(InFile);
8338  HOffset[0] = Utilities->LoadFileInt(InFile);
8339  HOffset[1] = Utilities->LoadFileInt(InFile);
8340  HOffset[2] = Utilities->LoadFileInt(InFile);
8341  HOffset[3] = Utilities->LoadFileInt(InFile);
8342  VOffset[0] = Utilities->LoadFileInt(InFile);
8343  VOffset[1] = Utilities->LoadFileInt(InFile);
8344  VOffset[2] = Utilities->LoadFileInt(InFile);
8345  VOffset[3] = Utilities->LoadFileInt(InFile);
8346  PlotElement[0] = Utilities->LoadFileInt(InFile);
8347  PlotElement[1] = Utilities->LoadFileInt(InFile);
8348  PlotElement[2] = Utilities->LoadFileInt(InFile);
8349  PlotElement[3] = Utilities->LoadFileInt(InFile);
8350  PlotEntryPos[0] = Utilities->LoadFileInt(InFile);
8351  PlotEntryPos[1] = Utilities->LoadFileInt(InFile);
8352  PlotEntryPos[2] = Utilities->LoadFileInt(InFile);
8353  PlotEntryPos[3] = Utilities->LoadFileInt(InFile);
8355  Straddle = (TStraddle)(Utilities->LoadFileInt(InFile));
8356  NextTrainID = Utilities->LoadFileInt(InFile);
8357  // will be same for all but best to save all anyway
8358  TrainID = Utilities->LoadFileInt(InFile);
8359  LeadElement = Utilities->LoadFileInt(InFile);
8360  LeadEntryPos = Utilities->LoadFileInt(InFile);
8361  LeadExitPos = Utilities->LoadFileInt(InFile);
8362  MidElement = Utilities->LoadFileInt(InFile);
8363  MidEntryPos = Utilities->LoadFileInt(InFile);
8364  MidExitPos = Utilities->LoadFileInt(InFile);
8365  LagElement = Utilities->LoadFileInt(InFile);
8366  LagEntryPos = Utilities->LoadFileInt(InFile);
8367  LagExitPos = Utilities->LoadFileInt(InFile);
8368  int ColourNumber = TColor(Utilities->LoadFileInt(InFile));
8369 
8370  if(ColourNumber == 0)
8371  {
8373  }
8374  else if(ColourNumber == 1)
8375  {
8377  }
8378  else if(ColourNumber == 2)
8379  {
8381  }
8382  else if(ColourNumber == 3)
8383  {
8385  }
8386  else if(ColourNumber == 4)
8387  {
8389  }
8390  else if(ColourNumber == 5)
8391  {
8393  }
8394  else if(ColourNumber == 6)
8395  {
8397  }
8398  else if(ColourNumber == 7)
8399  {
8401  }
8402  else if(ColourNumber == 8)
8403  {
8405  }
8406  else if(ColourNumber == 9)
8407  {
8409  }
8410  else if(ColourNumber == 10)
8411  {
8413  }
8414  else if(ColourNumber == 11)
8415  {
8417  }
8418  else if(ColourNumber == 12)
8419  {
8421  }
8422  else if(ColourNumber == 13)
8423  {
8425  }
8426  else if(ColourNumber == 14)
8427  {
8428  BackgroundColour = clTrainFailedBackground; // added at v2.4.0
8429 
8430  }
8431  // additional data
8433  // sets the BackgroundColour to the loaded value
8434  bool ForwardHeadCode = Utilities->LoadFileBool(InFile);
8435 
8436  if(ForwardHeadCode)
8437  {
8438  for(int x = 0; x < 4; x++)
8439  {
8441  }
8442  }
8443  else
8444  {
8445  for(int x = 0; x < 4; x++)
8446  {
8447  HeadCodePosition[x] = HeadCodeGrPtr[3 - x];
8448  }
8449  }
8450  // if crashed & in timetable mode then change FrontCodePtr to black, if in signaller mode then change to blue whether crashed or not
8451  if(TrainMode == Timetable)
8452  {
8453  if(Crashed)
8454  {
8456  }
8457  else
8458  {
8460  }
8461  }
8462  else
8463  {
8465  }
8467  // pick up background bitmaps, none if MidLag as no train plotted - entering at continuation
8468  if(Straddle == LeadMid)
8469  {
8470  if(LeadElement > -1)
8471  {
8473  }
8474  if(LeadElement > -1)
8475  {
8477  }
8478  if(MidElement > -1)
8479  {
8481  }
8482  if(MidElement > -1)
8483  {
8485  }
8486  }
8487  else if(Straddle == LeadMidLag)
8488  {
8489  if(LeadElement > -1)
8490  {
8492  }
8493  if(MidElement > -1)
8494  {
8496  }
8497  if(MidElement > -1)
8498  {
8500  }
8501  if(LagElement > -1)
8502  {
8504  }
8505  }
8506  int TrainDataEntryValue = Utilities->LoadFileInt(InFile);
8507 
8508  TrainDataEntryPtr = &(TrainController->TrainDataVector.at(0)) + TrainDataEntryValue;
8509  int ActionVectorEntryValue = Utilities->LoadFileInt(InFile);
8510 
8511  ActionVectorEntryPtr = &(TrainDataEntryPtr->ActionVector.at(0)) + ActionVectorEntryValue;
8512 
8513  // need to set the TrainID if arriving at a continuation but hasn't been plotted yet
8514  if(LeadElement > -1)
8515  // need to include this in case train exiting & no lead element
8516  {
8518  {
8519  Track->TrackElementAt(668, LeadElement).TrainIDOnElement = TrainID; // no need to stop gap flashing if a continuation
8520  }
8521  }
8522  AValue = sqrt(2 * PowerAtRail / Mass);
8523 
8524  AnsiString LocationAndMarker = Utilities->LoadFileString(InFile);
8525 
8526  // possible RestoreTimetableLocation + Marker, where Marker is
8527  // "*****0" for !StoppedWithoutPower and "*****1" otherwise (from v2.4.0)
8528  // Note: including RestoreTimetableLocation with the marker is to correct an oversight - RestoreTimetableLocation should have been saved earlier
8529  // added at beta v0.2e
8530  if((LocationAndMarker[1] != '*') && (LocationAndMarker.Length() > 6))
8531  // name not allowed to include the '*' character
8532  {
8533  AnsiString Location = LocationAndMarker.SubString(1, LocationAndMarker.Length() - 6);
8534  bool GiveMessagesFalse = false;
8535  bool CheckLocationsExistInRailwayTrue = true;
8536  if(TrainController->CheckLocationValidity(3, Location, GiveMessagesFalse, CheckLocationsExistInRailwayTrue))
8537  {
8538  // otherwise take no action
8539  RestoreTimetableLocation = Location;
8540  }
8541  }
8542  AnsiString Marker = LocationAndMarker.SubString(LocationAndMarker.Length() - 5, 6);
8543 
8544  StoppedWithoutPower = false;
8545  if(Marker[6] == '1')
8546  {
8547  StoppedWithoutPower = true;
8548  }
8549  Utilities->CallLogPop(1458);
8550 }
8551 
8552 // ---------------------------------------------------------------------------
8553 
8554 bool TTrain::CheckOneSessionTrain(std::ifstream &InFile)
8555 {
8557  {
8558  return(false); // HeadCode
8559 
8560  }
8561  if(!Utilities->CheckFileInt(InFile, 0, 1000000))
8562  {
8563  return(false); // RearStartElement
8564 
8565  }
8566  if(!Utilities->CheckFileInt(InFile, 0, 3))
8567  {
8568  return(false); // RearStartExitPos
8569 
8570  }
8571  if(!Utilities->CheckFileInt(InFile, 0, MaximumSpeedLimit))
8572  {
8573  return(false); // StartSpeed
8574 
8575  }
8576  if(!Utilities->CheckFileInt(InFile, 0, MaximumSpeedLimit))
8577  {
8578  return(false); // SignallerMaxSpeed
8579 
8580  }
8581  if(!Utilities->CheckFileBool(InFile))
8582  {
8583  return(false); // HoldAtLocationInTTMode
8584 
8585  }
8586  if(!Utilities->CheckFileInt(InFile, 0, 5760))
8587  {
8588  return(false); // RepeatNumber (max 96 x 60 at 1 min intervals)
8589 
8590  }
8591  if(!Utilities->CheckFileInt(InFile, 0, 5760))
8592  {
8593  return(false); // IncrementalMinutes (max 96 x 60)
8594 
8595  }
8596  if(!Utilities->CheckFileInt(InFile, 0, 1000000))
8597  {
8598  return(false); // IncrementalDigits
8599 
8600  }
8601  if(!Utilities->CheckFileInt(InFile, 0, 10000000))
8602  {
8603  return(false); // Mass
8604 
8605  }
8606  if(!Utilities->CheckFileInt(InFile, 0, 100000000))
8607  {
8608  return(false);
8609  }
8610  // FrontElementSpeedLimit - changed at v2.1.0 - effectively
8611  // not checked so as to allow for legacy session files, for new session files limit is set when loaded, see above
8612  if(!Utilities->CheckFileInt(InFile, 0, 10000000))
8613  {
8614  return(false); // FrontElementLength
8615 
8616  }
8617  if(!Utilities->CheckFileDouble(InFile))
8618  {
8619  return(false); // EntrySpeed
8620 
8621  }
8622  if(!Utilities->CheckFileDouble(InFile))
8623  {
8624  return(false); // ExitSpeedHalf
8625 
8626  }
8627  if(!Utilities->CheckFileDouble(InFile))
8628  {
8629  return(false); // ExitSpeedFull
8630 
8631  }
8632  if(!Utilities->CheckFileDouble(InFile))
8633  {
8634  return(false); // TimetableMaxRunningSpeed
8635 
8636  }
8637  if(!Utilities->CheckFileDouble(InFile))
8638  {
8639  return(false); // MaxRunningSpeed
8640 
8641  }
8642  if(!Utilities->CheckFileDouble(InFile))
8643  {
8644  return(false); // MaxExitSpeed
8645 
8646  }
8647  if(!Utilities->CheckFileDouble(InFile))
8648  {
8649  return(false); // MaxBrakeRate
8650 
8651  }
8652  if(!Utilities->CheckFileDouble(InFile))
8653  {
8654  return(false); // BrakeRate
8655 
8656  }
8657  if(!Utilities->CheckFileDouble(InFile))
8658  {
8659  return(false); // PowerAtRail
8660 
8661  }
8662  if(!Utilities->CheckFileBool(InFile))
8663  {
8664  return(false); // FirstHalfMove
8665 
8666  }
8667  if(!Utilities->CheckFileBool(InFile))
8668  {
8669  return(false); // OneLengthAccelDecel
8670 
8671  }
8672  if(!Utilities->CheckFileDouble(InFile))
8673  {
8674  return(false); // double(EntryTime)
8675 
8676  }
8677  if(!Utilities->CheckFileDouble(InFile))
8678  {
8679  return(false); // double(ExitTimeHalf)
8680 
8681  }
8682  if(!Utilities->CheckFileDouble(InFile))
8683  {
8684  return(false); // double(ExitTimeFull)
8685 
8686  }
8687  if(!Utilities->CheckFileDouble(InFile))
8688  {
8689  return(false); // double(ReleaseTime)
8690 
8691  }
8692  if(!Utilities->CheckFileDouble(InFile))
8693  {
8694  return(false); // double(TRSTime)
8695 
8696  }
8697  if(!Utilities->CheckFileDouble(InFile))
8698  {
8699  return(false); // double(LastActionTime)
8700 
8701  }
8702  if(!Utilities->CheckFileBool(InFile))
8703  {
8704  return(false); // CallingOnFlag
8705 
8706  }
8707  if(!Utilities->CheckFileBool(InFile))
8708  {
8709  return(false); // BeingCalledOn
8710 
8711  }
8712  if(!Utilities->CheckFileBool(InFile))
8713  {
8714  return(false); // DepartureTimeSet
8715 
8716  }
8717  if(!Utilities->CheckFileInt(InFile, 0, 2))
8718  {
8719  return(false); // (short)TrainMode
8720 
8721  }
8722  if(!Utilities->CheckFileBool(InFile))
8723  {
8724  return(false); // TimetableFinished
8725 
8726  }
8727  if(!Utilities->CheckFileBool(InFile))
8728  {
8729  return(false); // LastActionDelayFlag
8730 
8731  }
8732  if(!Utilities->CheckFileBool(InFile))
8733  {
8734  return(false); // SignallerRemoved
8735 
8736  }
8737  if(!Utilities->CheckFileBool(InFile))
8738  {
8739  return(false); // TerminatedMessageSent
8740 
8741  }
8742  if(!Utilities->CheckFileBool(InFile))
8743  {
8744  return(false); // Derailed
8745 
8746  }
8747  if(!Utilities->CheckFileBool(InFile))
8748  {
8749  return(false); // DerailPending
8750 
8751  }
8752  if(!Utilities->CheckFileBool(InFile))
8753  {
8754  return(false); // Crashed
8755 
8756  }
8757  if(!Utilities->CheckFileBool(InFile))
8758  {
8759  return(false); // StoppedAtBuffers
8760 
8761  }
8762  if(!Utilities->CheckFileBool(InFile))
8763  {
8764  return(false); // StoppedAtSignal
8765 
8766  }
8767  if(!Utilities->CheckFileBool(InFile))
8768  {
8769  return(false); // StoppedAtLocation
8770 
8771  }
8772  if(!Utilities->CheckFileBool(InFile))
8773  {
8774  return(false); // SignallerStopped
8775 
8776  }
8777  if(!Utilities->CheckFileBool(InFile))
8778  {
8779  return(false); // StoppedAfterSPAD
8780 
8781  }
8782  if(!Utilities->CheckFileBool(InFile))
8783  {
8784  return(false); // StoppedForTrainInFront
8785 
8786  }
8787  if(!Utilities->CheckFileBool(InFile))
8788  {
8789  return(false); // NotInService
8790 
8791  }
8792  if(!Utilities->CheckFileBool(InFile))
8793  {
8794  return(false); // Plotted
8795 
8796  }
8797  if(!Utilities->CheckFileBool(InFile))
8798  {
8799  return(false); // TrainGone
8800 
8801  }
8802  if(!Utilities->CheckFileBool(InFile))
8803  {
8804  return(false); // SPADFlag
8805 
8806  }
8807  if(!Utilities->CheckFileBool(InFile))
8808  {
8809  return(false); // TimeTimeLocArrived
8810 
8811  }
8812  if(!Utilities->CheckFileInt(InFile, 0, 15))
8813  {
8814  return(false); // HOffset[0]
8815 
8816  }
8817  if(!Utilities->CheckFileInt(InFile, 0, 15))
8818  {
8819  return(false); // HOffset[1]
8820 
8821  }
8822  if(!Utilities->CheckFileInt(InFile, 0, 15))
8823  {
8824  return(false); // HOffset[2]
8825 
8826  }
8827  if(!Utilities->CheckFileInt(InFile, 0, 15))
8828  {
8829  return(false); // HOffset[3]
8830 
8831  }
8832  if(!Utilities->CheckFileInt(InFile, 0, 15))
8833  {
8834  return(false); // VOffset[0]
8835 
8836  }
8837  if(!Utilities->CheckFileInt(InFile, 0, 15))
8838  {
8839  return(false); // VOffset[1]
8840 
8841  }
8842  if(!Utilities->CheckFileInt(InFile, 0, 15))
8843  {
8844  return(false); // VOffset[2]
8845 
8846  }
8847  if(!Utilities->CheckFileInt(InFile, 0, 15))
8848  {
8849  return(false); // VOffset[3]
8850 
8851  }
8852  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
8853  {
8854  return(false); // PlotElement[0]
8855 
8856  }
8857  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
8858  {
8859  return(false); // PlotElement[1]
8860 
8861  }
8862  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
8863  {
8864  return(false); // PlotElement[2]
8865 
8866  }
8867  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
8868  {
8869  return(false); // PlotElement[3]
8870 
8871  }
8872  if(!Utilities->CheckFileInt(InFile, 0, 3))
8873  {
8874  return(false); // PlotEntryPos[0]
8875 
8876  }
8877  if(!Utilities->CheckFileInt(InFile, 0, 3))
8878  {
8879  return(false); // PlotEntryPos[1]
8880 
8881  }
8882  if(!Utilities->CheckFileInt(InFile, 0, 3))
8883  {
8884  return(false); // PlotEntryPos[2]
8885 
8886  }
8887  if(!Utilities->CheckFileInt(InFile, 0, 3))
8888  {
8889  return(false); // PlotEntryPos[3]
8890 
8891  }
8892  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
8893  {
8894  return(false); // TrainCrashedInto
8895 
8896  }
8897  if(!Utilities->CheckFileInt(InFile, 0, 2))
8898  {
8899  return(false); // (short)Straddle
8900 
8901  }
8902  if(!Utilities->CheckFileInt(InFile, 0, 1000000))
8903  {
8904  return(false); // NextTrainID
8905 
8906  }
8907  if(!Utilities->CheckFileInt(InFile, 0, 1000000))
8908  {
8909  return(false); // TrainID
8910 
8911  }
8912  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
8913  {
8914  return(false); // LeadElement
8915 
8916  }
8917  if(!Utilities->CheckFileInt(InFile, 0, 3))
8918  {
8919  return(false); // LeadEntryPos
8920 
8921  }
8922  if(!Utilities->CheckFileInt(InFile, 0, 3))
8923  {
8924  return(false); // LeadExitPos
8925 
8926  }
8927  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
8928  {
8929  return(false); // MidElement
8930 
8931  }
8932  if(!Utilities->CheckFileInt(InFile, 0, 3))
8933  {
8934  return(false); // MidEntryPos
8935 
8936  }
8937  if(!Utilities->CheckFileInt(InFile, 0, 3))
8938  {
8939  return(false); // MidExitPos
8940 
8941  }
8942  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
8943  {
8944  return(false); // LagElement
8945 
8946  }
8947  if(!Utilities->CheckFileInt(InFile, 0, 3))
8948  {
8949  return(false); // LagEntryPos
8950 
8951  }
8952  if(!Utilities->CheckFileInt(InFile, 0, 3))
8953  {
8954  return(false); // LagExitPos
8955 
8956  }
8957  if(!Utilities->CheckFileInt(InFile, 0, 14))
8958  {
8959  return(false);
8960  }
8961  // Background colour number //14 is failed colour at v2.4.0
8962  if(!Utilities->CheckFileBool(InFile))
8963  {
8964  return(false); // ForwardHeadCode
8965 
8966  }
8967  if(!Utilities->CheckFileInt(InFile, 0, 10000))
8968  {
8969  return(false); // TrainDataEntryValue
8970 
8971  }
8972  if(!Utilities->CheckFileInt(InFile, 0, 10000))
8973  {
8974  return(false); // ActionVectorEntryValue
8975 
8976  }
8978  {
8979  return(false); // End of train marker + possible RestoreTimetableLocation
8980 
8981  }
8982  // and StoppedWithoutPower flag
8983  return(true);
8984 }
8985 
8986 // ---------------------------------------------------------------------------
8987 
8988 void TTrain::PlotTrainInZoomOutMode(int Caller, bool Flash)
8989 {
8990  // order below reflects significance so earlier shows first, as may have more than one flag set
8991  // only plot flashing trains when Flash is true
8992 
8993 /*
8994  clCrashedBackground (TColor)0x0000FF red
8995  clDerailedBackground (TColor)0x0000FF red
8996  clSPADBackground (TColor)0x00FFFF yellow
8997  clTrainFailedBackground (TColor)0x0066FF orange new at v2.4.0
8998  clCallOnBackground (TColor)0xFF33FF light magenta
8999  clSignalStopBackground (TColor)0x00FF66 green
9000  clBufferAttentionNeeded (TColor)0xFFFF00 cyan
9001  clStationStopBackground (TColor)0xCCFFCC pale green
9002  clTRSBackground (TColor)0xFFCCFF light pink
9003  clBufferStopBackground (TColor)0xFFFFCC pale cyan
9004  clStoppedTrainInFront (TColor)0xFF9999 lavender blue
9005  clSignallerStopped (TColor)0x99CCFF caramel
9006  clNormalBackground (TColor)0xCCCCCC grey
9007 */
9008 
9009  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrainInZoomOutMode" + "," + HeadCode);
9010  bool HideFlashingTrain = true;
9011  // hide it when Flash false so it blinks on and off
9012  // if don't hide it it stays displayed all the time
9013  Graphics::TBitmap *SmallTrainBitmap;
9014 
9015  // NB ensure retain same order as zoomed in order so colours correspond
9017  {
9018  TrainController->CrashWarning = true;
9019  SmallTrainBitmap = RailGraphics->smRed;
9020  }
9022  {
9024  SmallTrainBitmap = RailGraphics->smRed;
9025  }
9027  {
9028  TrainController->SPADWarning = true;
9029  SmallTrainBitmap = RailGraphics->smYellow;
9030  }
9032  {
9034  SmallTrainBitmap = RailGraphics->smOrange;
9035  }
9037  {
9039  SmallTrainBitmap = RailGraphics->smMagenta;
9040  }
9042  {
9044  SmallTrainBitmap = RailGraphics->smBrightGreen;
9045  }
9047  {
9049  SmallTrainBitmap = RailGraphics->smCyan;
9050  }
9052  {
9053  SmallTrainBitmap = RailGraphics->smPaleGreen;
9054  HideFlashingTrain = false;
9055  }
9057  {
9058  SmallTrainBitmap = RailGraphics->smCyan;
9059  HideFlashingTrain = false;
9060  }
9062  {
9063  SmallTrainBitmap = RailGraphics->smLightBlue;
9064  HideFlashingTrain = false;
9065  }
9067  {
9068  SmallTrainBitmap = RailGraphics->smCaramel;
9069  HideFlashingTrain = false;
9070  }
9071  else
9072  {
9073  SmallTrainBitmap = RailGraphics->smBlack; // moving
9074  HideFlashingTrain = false;
9075  }
9076  // now plot the new train
9077  // just plot lead & mid, unless lead == -1 in which case plot mid & lag
9078  if((LeadElement > -1) && (!HideFlashingTrain || Flash))
9079  {
9080  Display->PlotSmallOutput(4, Track->TrackElementAt(441, LeadElement).HLoc * 4, Track->TrackElementAt(442, LeadElement).VLoc * 4, SmallTrainBitmap);
9081  }
9082  if((MidElement > -1) && (!HideFlashingTrain || Flash))
9083  {
9084  Display->PlotSmallOutput(5, Track->TrackElementAt(443, MidElement).HLoc * 4, Track->TrackElementAt(444, MidElement).VLoc * 4, SmallTrainBitmap);
9085  }
9086  if((LeadElement == -1) && (LagElement > -1) && (!HideFlashingTrain || Flash))
9087  {
9088  Display->PlotSmallOutput(6, Track->TrackElementAt(445, LagElement).HLoc * 4, Track->TrackElementAt(446, LagElement).VLoc * 4, SmallTrainBitmap);
9089  }
9093  Utilities->CallLogPop(1459);
9094 }
9095 
9096 // ---------------------------------------------------------------------------
9097 
9099 {
9100  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",UnplotTrainInZoomOutMode," + AnsiString(TrainID) + "," + HeadCode);
9101  if(!Display->ZoomOutFlag)
9102  {
9103  Utilities->CallLogPop(1304);
9104  return;
9105  }
9106  for(int y = 0; y < 3; y++)
9107  {
9108  if(OldZoomOutElement[y] > -1)
9109  {
9110  bool FoundFlag = false;
9111  TTrackElement ATElement = Track->TrackElementAt(717, OldZoomOutElement[y]);
9112  TTrackElement IATElement1, IATElement2;
9113  // default elements to begin with
9114  Display->PlotSmallOutput(7, ATElement.HLoc * 4, ATElement.VLoc * 4, RailGraphics->smSolidBgnd); // plot the blank
9115  TTrack::TIMPair IMPair = Track->GetVectorPositionsFromInactiveTrackMap(14, ATElement.HLoc, ATElement.VLoc, FoundFlag);
9116  // Note, have to plot inactives before track because track has to overwrite NamedLocationElements
9117  if(FoundFlag)
9118  {
9119  IATElement1 = Track->InactiveTrackElementAt(87, IMPair.first);
9120  Display->PlotSmallOutput(8, IATElement1.HLoc * 4, IATElement1.VLoc * 4, IATElement1.SmallGraphicPtr);
9121  if(IMPair.first != IMPair.second)
9122  {
9123  IATElement2 = Track->InactiveTrackElementAt(88, IMPair.second);
9124  Display->PlotSmallOutput(9, IATElement2.HLoc * 4, IATElement2.VLoc * 4, IATElement2.SmallGraphicPtr);
9125  }
9126  }
9127  Display->PlotSmallOutput(10, ATElement.HLoc * 4, ATElement.VLoc * 4, ATElement.SmallGraphicPtr);
9128  }
9129  }
9130  Utilities->CallLogPop(1305);
9131 }
9132 
9133 // ---------------------------------------------------------------------------
9134 
9135 bool TTrain::TrainAtLocation(int Caller, AnsiString &LocationName)
9136 {
9137  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainAtLocation" + "," + HeadCode);
9138  LocationName = "";
9139  if(!RevisedStoppedAtLoc())
9140  {
9141  Utilities->CallLogPop(1398);
9142  return(false);
9143  }
9144  if(LeadElement > -1)
9145  {
9147  }
9148  if((LocationName == "") && (MidElement > -1))
9149  {
9150  LocationName = Track->TrackElementAt(682, MidElement).ActiveTrackElementName;
9151  }
9152  if((LocationName == "") && (LagElement > -1))
9153  {
9154  LocationName = Track->TrackElementAt(683, LagElement).ActiveTrackElementName;
9155  }
9156  if(LocationName == "")
9157  {
9158  throw Exception("Error - Location name not set in TrainAtLocation");
9159  }
9160  Utilities->CallLogPop(1399);
9161  return(true);
9162 }
9163 
9164 // ---------------------------------------------------------------------------
9165 
9166 void TTrain::PlotTrain(int Caller, TDisplay *Disp)
9167 {
9168  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrain" + "," + HeadCode);
9169  for(int x = 0; x < 4; x++)
9170  {
9171  PlotTrainGraphic(7, x, Disp);
9172  }
9173  Utilities->CallLogPop(647);
9174 }
9175 
9176 // ---------------------------------------------------------------------------
9177 
9178 void TTrain::WriteTrainToImage(int Caller, Graphics::TBitmap *Bitmap)
9179 {
9180  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",WriteTrainToImage" + "," + HeadCode);
9181  for(int x = 0; x < 4; x++)
9182  {
9183  if(PlotElement[x] > -1)
9184  {
9185  Bitmap->Canvas->Draw(((Track->TrackElementAt(744, PlotElement[x]).HLoc - Track->GetHLocMin()) * 16 + HOffset[x]),
9186  ((Track->TrackElementAt(745, PlotElement[x]).VLoc - Track->GetVLocMin()) * 16 + VOffset[x]), HeadCodePosition[x]);
9187  }
9188  }
9189  Utilities->CallLogPop(1708);
9190 }
9191 
9192 // ---------------------------------------------------------------------------
9193 
9194 bool TTrain::LinkOccupied(int Caller, int TrackVectorPosition, int LinkNumber) // added at v1.2.0
9195 {
9196  // return true for any part of train occupying LinkNumber at TrackVectorPosition, false for anything else, including no LinkNumber & no TrackVectorPosition
9197  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LinkOccupied," + AnsiString(TrackVectorPosition) + "," +
9198  AnsiString(LinkNumber) + "," + HeadCode);
9199 
9200 /* Note on Straddle: Straddle defines the actual train position wrt Lag, Mid & Lead elements at all times other than within UpdateTrain. Is only MidLag outside UpdateTrain
9201  on first entry at a continuation (with no train plotted), and that has no relevance here. In all other cases it is either LeadMid (when train fully
9202  on Lead & Mid elements) or LeadMidLag (when train straddling 3 elements).
9203 */
9204 
9205  // note that MidElement always fully occupied
9206  if((MidElement == TrackVectorPosition) && ((Track->TrackElementAt(883, TrackVectorPosition).Link[MidEntryPos] == LinkNumber) || (Track->TrackElementAt(884,
9207  TrackVectorPosition).Link[MidExitPos] == LinkNumber)))
9208  {
9209  Utilities->CallLogPop(2005);
9210  return(true);
9211  }
9212  if(Straddle == LeadMid)
9213  {
9214  if((LeadElement == TrackVectorPosition) && ((Track->TrackElementAt(885, TrackVectorPosition).Link[LeadEntryPos] == LinkNumber) ||
9215  (Track->TrackElementAt(886, TrackVectorPosition).Link[LeadExitPos] == LinkNumber)))
9216  {
9217  Utilities->CallLogPop(2006);
9218  return(true);
9219  }
9220  }
9221  else if(Straddle == LeadMidLag)
9222  {
9223  if((LeadElement == TrackVectorPosition) && (Track->TrackElementAt(887, TrackVectorPosition).Link[LeadEntryPos] == LinkNumber))
9224  // only interested in LeadEntryPos as train not occupying ExitPos yet
9225  {
9226  Utilities->CallLogPop(2007);
9227  return(true);
9228  }
9229  else if((LagElement == TrackVectorPosition) && (Track->TrackElementAt(888, TrackVectorPosition).Link[LagExitPos] == LinkNumber))
9230  // only interested in LagExitPos as train has left EntryPos
9231  {
9232  Utilities->CallLogPop(2008);
9233  return(true);
9234  }
9235  }
9236  Utilities->CallLogPop(2009);
9237  return(false);
9238 }
9239 
9240 // ---------------------------------------------------------------------------
9241 
9242 float TTrain::CalcTimeToAct(int Caller, float &TimeToExit, THVShortPair &ExitPair) // only called for running trains.
9247 // TimeToExit & ExitPair added for multiplayer
9248 {
9249  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CalcTimeToAct, " + HeadCode);
9250  int DistanceToRedSignal = 0, DistanceToExit = -1;
9251  float TimeToAct = 0, LastTimeToExit = TimeToExit;
9252  TimeToExit = -1;
9253  ExitPair.first = -1;
9254  ExitPair.second = -1;
9255  float MinsEarly = 0; //added at v2.6.1
9256  TDateTime DepartureTime; //added at v2.6.1 //ArrivalTime used instead of this at v2.9.0 but still calculate it in case need it later for some reason
9257  TDateTime ArrivalTime; //added at v2.9.0 as MinsEarly used DepartureTime which wasn't correct
9258  float TempTTE;
9259 
9260  if(TrainFailed && Stopped() && (TrainMode != Signaller))
9261  {
9262  Utilities->CallLogPop(2147);
9263  return(0); // time to act now, time to exit set above
9264  }
9265  if(SignallerStopped)
9266  {
9267  Utilities->CallLogPop(2080);
9268  return(-1); //time to exit set above
9269  }
9270  if(BeingCalledOn) //added at v2.7.0 so zero time to act cancelled right away
9271  {
9272  Utilities->CallLogPop(2266);
9273  return(-1); // time to exit set above
9274  }
9275 
9276  // check if exiting at a continuation, if so there's no action time needed but still need exit time & ExitPair
9277  if((LeadElement == -1) && (MidElement == -1) && (LagElement > -1) && (Track->TrackElementAt(1411, LagElement).TrackType == Continuation)
9278  /*&& (ExitSpeedFull > 1)*/) //LagElement is the exit //ExitSpeedFull removed at v2.12.0 because of Cameron Neasom's error 28/01/22 - braking as entered continuation
9279  {
9280  if(Straddle == LeadMidLag) //only half of rear train element on exit, 0.5 lengths to exit
9281  {
9282  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9283  {
9284  TempTTE = (0.5 * Track->TrackElementAt(1412, LagElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9285  if(TempTTE < LastTimeToExit)
9286  {
9287  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9288  }
9289  else
9290  {
9291  TimeToExit = LastTimeToExit;
9292  }
9293  }
9294  else
9295  {
9296  TimeToExit = LastTimeToExit;
9297  }
9298  ExitPair.first = Track->TrackElementAt(1413, LagElement).HLoc;
9299  ExitPair.second = Track->TrackElementAt(1414, LagElement).VLoc;
9300  Utilities->CallLogPop(2342);
9301  return(-1);
9302  }
9303  else
9304  {
9305  TimeToExit = 0; //all train exited
9306  ExitPair.first = Track->TrackElementAt(1415, LagElement).HLoc;
9307  ExitPair.second = Track->TrackElementAt(1416, LagElement).VLoc;
9308  Utilities->CallLogPop(2343);
9309  return(-1);
9310  }
9311  }
9312  if((LeadElement == -1) && (MidElement > -1) && (Track->TrackElementAt(1417, MidElement).TrackType == Continuation)/* && (ExitSpeedFull > 1)*/)
9313  //here MidElement is the exit //ExitSpeedFull removed at v2.12.0 because of Cameron Neasom's error 28/01/22 - braking as entered continuation
9314  {
9315  if(Straddle == LeadMidLag) //front element of train half off the exit, 1.5 lengths to exit
9316  {
9317  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9318  {
9319  TempTTE = (1.5 * Track->TrackElementAt(1418, MidElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9320  if(TempTTE < LastTimeToExit)
9321  {
9322  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9323  }
9324  else
9325  {
9326  TimeToExit = LastTimeToExit;
9327  }
9328  }
9329  else
9330  {
9331  TimeToExit = LastTimeToExit;
9332  }
9333  ExitPair.first = Track->TrackElementAt(1419, MidElement).HLoc;
9334  ExitPair.second = Track->TrackElementAt(1420, MidElement).VLoc;
9335  Utilities->CallLogPop(2344);
9336  return(-1);
9337  }
9338  else //front element of train fully off the exit, one length to exit
9339  {
9340  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9341  {
9342  TempTTE = (Track->TrackElementAt(1421, MidElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9343  if(TempTTE < LastTimeToExit)
9344  {
9345  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9346  }
9347  else
9348  {
9349  TimeToExit = LastTimeToExit;
9350  }
9351  }
9352  else
9353  {
9354  TimeToExit = LastTimeToExit;
9355  }
9356  ExitPair.first = Track->TrackElementAt(1422, MidElement).HLoc;
9357  ExitPair.second = Track->TrackElementAt(1423, MidElement).VLoc;
9358  Utilities->CallLogPop(2345);
9359  return(-1);
9360  }
9361  }
9362  if(LeadElement > -1)
9363  {
9365  /* && (ExitSpeedFull > 1)*/) //LeadElement is the exit. If LeadMidLag have lead half on exit or if LeadMid have lead full on exit
9366  { //if LeadMidLag have 2.5 lengths to exit, else have 2 lengths to exit
9367  //ExitSpeedFull removed at v2.12.0 because of Cameron Neasom's error 28/01/22 - braking as entered continuation
9368  if(Straddle == LeadMidLag)
9369  {
9370  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9371  {
9372  TempTTE = (2.5 * Track->TrackElementAt(1426, LeadElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9373  if(TempTTE < LastTimeToExit)
9374  {
9375  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9376  }
9377  else
9378  {
9379  TimeToExit = LastTimeToExit;
9380  }
9381  }
9382  else
9383  {
9384  TimeToExit = LastTimeToExit;
9385  }
9386  ExitPair.first = Track->TrackElementAt(1427, LeadElement).HLoc;
9387  ExitPair.second = Track->TrackElementAt(1428, LeadElement).VLoc;
9388  Utilities->CallLogPop(2346);
9389  return(-1);
9390  }
9391  else
9392  {
9393  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9394  {
9395  TempTTE = (2 * Track->TrackElementAt(1429, LeadElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9396  if(TempTTE < LastTimeToExit)
9397  {
9398  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9399  }
9400  else
9401  {
9402  TimeToExit = LastTimeToExit;
9403  }
9404  }
9405  else
9406  {
9407  TimeToExit = LastTimeToExit;
9408  }
9409  ExitPair.first = Track->TrackElementAt(1430, LeadElement).HLoc;
9410  ExitPair.second = Track->TrackElementAt(1431, LeadElement).VLoc;
9411  Utilities->CallLogPop(2347);
9412  return(-1);
9413  }
9414  }
9415  }
9416 //here if LeadElement > -1 and its forward connection also > -1
9417 
9418  // calc distance to next red signal
9419  if(!Stopped() || RevisedStoppedAtLoc())
9420  {
9421  int FirstPosToBeMeasured = Track->TrackElementAt(953, LeadElement).Conn[LeadExitPos];
9422  int FirstEntryPos = Track->TrackElementAt(954, LeadElement).ConnLinkPos[LeadExitPos];
9423  if((Straddle == LeadMidLag) && (TrainMode == Timetable))
9424 /* In TTMode it's important to set the first element to be measured ahead of the lead element only when the train fully on
9425  2 elements. Otherwise, if the train is only half on the lead element and approaching a station stop where the platform doesn't
9426  extend beyond the lead element stop point, the element ahead of the lead element is not a location whereas the ActionVector
9427  still points to the station stop location. In these circumstances the train hasn't yet stopped, so the dwell time at the
9428  stop isn't calculated, and the station to be stopped at isn't found as a future stop and nor are any other future stops
9429  because the ActionVector name never matches a future station. Hence all dwell times are omitted until the train lands fully
9430  on two elements. To avoid this when Straddle is LeadMidLag the first element to be measured is set to the lead element, so
9431  before the train has stopped the current station is still recognised as a future stop.
9432  In signaller mode stops don't count, and if pass stop signal command is given then when have LeadMidLag the current element
9433  becomes the signal, and the time to act indication becomes 'NOW'.
9434 */
9435  {
9436  FirstPosToBeMeasured = LeadElement;
9437  FirstEntryPos = LeadEntryPos;
9438  }
9439  float CurrentStopTime; // set to 0 at start of function
9440  float LaterStopTime; // set to 0 at start of function
9441  float RecoverableTime; // set to 0 at start of function
9442  int AvTrackSpeed; // set to zero at start of function
9443  bool SigControlAndCanPassRedSignal = ((TrainMode == Signaller) && AllowedToPassRedSignal);
9444  DistanceToRedSignal = TrainController->CalcDistanceToRedSignalandStopTime(0, FirstPosToBeMeasured, FirstEntryPos, SigControlAndCanPassRedSignal,
9445  ActionVectorEntryPtr, HeadCode, TrainID, CurrentStopTime, LaterStopTime, RecoverableTime, AvTrackSpeed, DistanceToExit, ExitPair);
9446 //at this point can't have both DistanceToRedSignal and DistanceToExit both set. Either both will be unset (-1) or one will be set.
9447 //Therefore since need to calculate the time for each in the same way use a generic
9448 
9449  if((DistanceToRedSignal == -1) && (DistanceToExit == -1))// both unset so no action needed
9450  {
9451  TimeToExit = -1;
9452  Utilities->CallLogPop(2076);
9453  return(-1);
9454  }
9455 //else one or other is set
9456  bool DistanceToRedSignalSet = (DistanceToRedSignal > -1);
9457  bool DistanceToExitSet = (DistanceToExit > -1);
9458  int GenericDistance = DistanceToRedSignal;
9459  if(DistanceToExitSet)
9460  {
9461  GenericDistance = DistanceToExit;
9462  }
9463 /* Have MinsDelayed; pos or neg,
9464  CurrentStopTime; pos or zero
9465  LaterStopTime; pos or zero
9466  RecoverableTime; pos or zero
9467 
9468  & from these calculate TotalStopTime. noting that:
9469  If stopped CurrentStopTime automatically adjusts for all early running and for as much late running as possible
9470  RecoverableTime always < LaterStopTime or both zero
9471  can't subtract more than RecoverableTime (MinsDelayed > 0)
9472  only subtract from LaterStopTime, not CurrentTime (MinsDelayed > 0)
9473  only subtract from LaterStopTime if LaterStopTime > 0 (MinsDelayed > 0)
9474  only add to LaterStopTime if LaterStopTime > 0 (MinsDelayed < 0)
9475  if running early & stopped at location CurrentStopTime will automatically include the excess
9476 */
9477  float TimeToSubtract, TotalStopTime;
9478  if(MinsDelayed > RecoverableTime)
9479  {
9480  TimeToSubtract = RecoverableTime;
9481  }
9482  else
9483  {
9484  TimeToSubtract = MinsDelayed; // may be negative;
9485  }
9486  if((AvTrackSpeed > 0) && (DistanceToStationStop <= GenericDistance) && (DistanceToStationStop > 0)) //protection against div by zero, not needed of no stop
9487  //before red signal, DistanceToStationStop != 0 as set to 0 if invalid
9488  //added at v2.6.1, DistanceToStationStop is calculated in SetTrainMovementValues, AvTrackSpeed is average to next red signal, but should be ok to use for
9489  //next station stop
9490  //after 2.7.0 changed (DistanceToStationStop < DistanceToRedSignal) to (DistanceToStationStop <= DistanceToRedSignal) because often have departure signal
9491  //next to the stop platform and if so the two distances are the same and the station stop time isn't included. Also after 2.7.0 used GetRepeatTime... to calc
9492  //departure time because ActionVectorEntryPtr->DepartureTime is the base time and therefore incorrect for repeats.
9493  //first find departure time from the next stop
9494  //at v2.9.0 changed MinsEarly calc to use ArrivalTime instead of departure time
9495  {
9496  if(ActionVectorEntryPtr->FormatType == TimeTimeLoc) //if already arrived then MinsEarly will be < 0 so becomes set to 0
9497  {
9500  MinsEarly = (double(ArrivalTime - TrainController->TTClockTime) * 86400 / 60) - (DistanceToStationStop * 3.6 / 60 / AvTrackSpeed);
9501  }
9502  else if((ActionVectorEntryPtr->FormatType == TimeLoc) && (ActionVectorEntryPtr->ArrivalTime != TDateTime(-1))) // not arrived yet
9503  {
9505  MinsEarly = (double(ArrivalTime - TrainController->TTClockTime) * 86400 / 60) - (DistanceToStationStop * 3.6 / 60 / AvTrackSpeed);
9506  if((ActionVectorEntryPtr + 1)->FormatType == TimeLoc)
9507  {
9508  // must be a departure
9509  DepartureTime = TrainController->GetRepeatTime(70, (ActionVectorEntryPtr + 1)->DepartureTime, RepeatNumber, IncrementalMinutes);
9510  }
9511  }
9512  else if((ActionVectorEntryPtr->FormatType == TimeLoc) && (ActionVectorEntryPtr->ArrivalTime == TDateTime(-1))) //already arrived
9513  {
9514  MinsEarly = 0;
9515  }
9516  if(MinsEarly < 0)
9517  {
9518  MinsEarly = 0;
9519  }
9520  }
9521  if(MinsDelayed < 0) // MinsDelayed < 0 means have arrived early at a station
9522  {
9523  if(CurrentStopTime > 0)
9524  {
9525  TotalStopTime = CurrentStopTime + LaterStopTime;
9526  }
9527  // stopped at loc, will depart on time
9528  else
9529  {
9530  TotalStopTime = LaterStopTime - MinsDelayed;
9531  }
9532  // not stopped, will depart on time at first later stop so add the delay
9533  }
9534  else if((MinsEarly > 0) && !Stopped()) //running early
9535  {
9536  TotalStopTime = LaterStopTime + MinsEarly;
9537  }
9538  else // on time or running late
9539  {
9540  if(LaterStopTime == 0)
9541  {
9542  TotalStopTime = CurrentStopTime;
9543  }
9544  // no later stops, if stopped now will depart as soon as possible,
9545  // if not stopped no stop times to add
9546  else
9547  {
9548  TotalStopTime = CurrentStopTime + LaterStopTime - TimeToSubtract; // later stops so deduct as much as can
9549  }
9550  }
9551  if(AvTrackSpeed < 30)
9552  {
9553  AvTrackSpeed = 30;
9554  }
9555  int Speed = AvTrackSpeed;
9556  if(AvTrackSpeed > int(MaxRunningSpeed))
9557  {
9558  Speed = int(MaxRunningSpeed);
9559  }
9560  if(TrainMode == Signaller)
9561  {
9562  Speed = SignallerMaxSpeed;
9563  TotalStopTime = 0;
9564  }
9565  if(DistanceToRedSignalSet)
9566  {
9567  TimeToAct = TotalStopTime + GenericDistance * 3.6 / 60 / Speed;
9568  // accel & decel taken into account in
9569  // CalcDistanceToRedSignalandStopTime
9570  // 3.6 converts Km/h to m/s & 60 converts seconds to minutes
9571  TimeToExit = -1;
9572  Utilities->CallLogPop(2079);
9573  return(TimeToAct);
9574  }
9575  else //DistanceToExitSet must be true
9576  {
9577  TimeToExit = TotalStopTime + GenericDistance * 3.6 / 60 / Speed;
9578  // accel & decel taken into account in
9579  // CalcDistanceToRedSignalandStopTime
9580  // 3.6 convertsKm/h to m/s & 60 converts seconds to minutes
9581  Utilities->CallLogPop(2370);
9582  return(-1); //no red signal so no time to act
9583  }
9584  }
9585  else // stopped not at location
9586  {
9588  {
9589  TimeToAct = 0.0;
9590  TimeToExit = -1;
9591  }
9592  if(StoppedWithoutPower) //added at v2.13.2 as this situation was missed & time to act was 0 [If train failed then covered above]
9593  {
9594  TimeToAct = -1;
9595  TimeToExit = -1;
9596  }
9597  // but if stopped at a signal & autosigs route after it then ok, provided signal not failed
9598  if(StoppedAtSignal)
9599  {
9600  int NextElement = Track->TrackElementAt(928, LeadElement).Conn[LeadExitPos];
9601  int NextEntryPos = Track->TrackElementAt(929, LeadElement).ConnLinkPos[LeadExitPos];
9602  bool NextElementFailed = Track->TrackElementAt(1548, NextElement).Failed; //added at v2.13.2
9603  int NextExitPos;
9604  if(Track->TrackElementAt(930, NextElement).TrackType == Points)
9605  {
9606  if((NextEntryPos == 0) || (NextEntryPos == 2))
9607  // leading entry point
9608  {
9609  if(Track->TrackElementAt(931, NextElement).Attribute == 0)
9610  {
9611  NextExitPos = 1;
9612  }
9613  else
9614  {
9615  NextExitPos = 3;
9616  }
9617  }
9618  else
9619  {
9620  NextExitPos = 0; // trailing entry point
9621  }
9622  }
9623  else
9624  {
9625  NextExitPos = Track->GetNonPointsOppositeLinkPos(NextEntryPos);
9626  }
9627  int NextButOneElement = Track->TrackElementAt(932, NextElement).Conn[NextExitPos];
9628  int NextButOneEntryPos = Track->TrackElementAt(933, NextElement).ConnLinkPos[NextExitPos];
9629  int RouteNumber; // holder for referenced value, not used
9630  if((AllRoutes->GetRouteTypeAndNumber(32, NextButOneElement, NextButOneEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute) && !NextElementFailed)
9631  { //NextElementFailed added at v2.13.2
9632  TimeToAct = -1;
9633  TimeToExit = -1;
9634  }
9635  }
9636  Utilities->CallLogPop(2074);
9637  return(TimeToAct);
9638  }
9639 }
9640 
9641 // ---------------------------------------------------------------------------
9642 
9644 {
9645  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainOnContinuation, " + HeadCode);
9646  if(LeadElement > -1)
9647  {
9649  {
9650  Utilities->CallLogPop(2148);
9651  return(true);
9652  }
9653  }
9654  if(MidElement > -1)
9655  {
9657  {
9658  Utilities->CallLogPop(2149);
9659  return(true);
9660  }
9661  }
9662  if(LagElement > -1)
9663  {
9665  {
9666  Utilities->CallLogPop(2150);
9667  return(true);
9668  }
9669  }
9670  Utilities->CallLogPop(2151);
9671  return(false);
9672 }
9673 
9674 // ---------------------------------------------------------------------------
9675 // TTrainController
9676 // ---------------------------------------------------------------------------
9677 
9679 {
9680  OnTimeArrivals = 0;
9681  LateArrivals = 0;
9682  EarlyArrivals = 0;
9683  OnTimePasses = 0;
9684  LatePasses = 0;
9685  EarlyPasses = 0;
9686  OnTimeExits = 0; //these 3 exits added at v2.9.2 - missed in error earlier
9687  LateExits = 0;
9688  EarlyExits = 0;
9689  OnTimeDeps = 0;
9690  LateDeps = 0;
9691  MissedStops = 0;
9692  OtherMissedEvents = 0;
9693  UnexpectedExits = 0;
9694  NumFailures = 0;
9695  IncorrectExits = 0;
9696  SPADEvents = 0;
9697  SPADRisks = 0;
9698  CrashedTrains = 0;
9699  Derailments = 0;
9700  TotArrDepPass = 0;
9701  TotLateArrMins = 0;
9702  TotEarlyArrMins = 0;
9703  TotLatePassMins = 0;
9704  TotEarlyPassMins = 0;
9705  TotLateExitMins = 0; //added at v2.9.1
9706  TotEarlyExitMins = 0; //added at v2.9.1
9707  TotLateDepMins = 0;
9708  ExcessLCDownMins = 0;
9709  SkippedTTEvents = 0; //added at v2.11.0
9710  TTClockTime = 0; // added for v0.6
9712  // added at v1.3.0 to ensure false at start
9713  OpTimeToActUpdateCounter = 0; // new v2.2.0
9714  OpActionPanelVisible = false; // new v2.2.0
9715  // reset all message flags, stops them being given twice (shouldn't be needed here but add for safety) //new at v2.4.0
9716  SSHigh = false;
9717  MRSHigh = false;
9718  MRSLow = false;
9719  MassHigh = false;
9720  BFHigh = false;
9721  BFLow = false;
9722  PwrHigh = false;
9723  SigSHigh = false;
9724  SigSLow = false;
9725  randomize();
9726  // to seed rand() & random() with a random number (see UpdateTrain)
9727 }
9728 
9729 // ---------------------------------------------------------------------------
9730 
9732 {
9733  for(unsigned int x = 0; x < TrainVector.size(); x++)
9734  {
9735  TrainVectorAt(32, x).DeleteTrain(4);
9736  }
9737  TrainVector.clear();
9738 }
9739 
9740 // ---------------------------------------------------------------------------
9741 
9742 void TTrainController::LogEvent(AnsiString Str)
9743 {
9744  AnsiString FullStr = Utilities->TimeStamp() + "," + TTClockTime.FormatString("hh:nn:ss") + "," + Str;
9745 
9746  // restrict to last 1000 entries
9747  Utilities->EventLog.push_back(FullStr);
9748  if(Utilities->EventLog.size() > 1000)
9749  {
9750  Utilities->EventLog.pop_front();
9751  }
9752 }
9753 
9754 // ---------------------------------------------------------------------------
9755 
9757 {
9758  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",Operate");
9759  bool ClockState = Utilities->Clock2Stopped;
9760  Utilities->Clock2Stopped = true;
9761  // new section dealing with Snt & Snt-sh additions
9762  // BUT don't add trains if points or route flashing [conditions added for Version 0.6 as a result of Najamuddin's error - 15/01/11] - wait until next
9763  // clock tick after stops flashing
9765  {
9766  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
9767  {
9768  TTrainDataEntry & TDEntry = TrainDataVector.at(x);
9769  const TActionVectorEntry &AVEntry0 = TDEntry.ActionVector.at(0);
9770  TActionEventType EventType = NoEvent;
9771  if(AVEntry0.Command == "Snt")
9772  {
9773  // calc below only for Snt & Snt-sh entries rather than all entries to save time
9774  const TActionVectorEntry &AVEntryLast = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
9775  int IncrementalMinutes = 0;
9776  int IncrementalDigits = 0;
9777  if(AVEntryLast.FormatType == Repeat)
9778  {
9779  IncrementalMinutes = AVEntryLast.RearStartOrRepeatMins;
9780  IncrementalDigits = AVEntryLast.FrontStartOrRepeatDigits;
9781  }
9782  if((AVEntryLast.FormatType == Repeat) && (TDEntry.NumberOfTrains < 2))
9783  {
9784  throw Exception("Error - Repeat entry && less than two trains for Snt entry: " + TDEntry.HeadCode);
9785  }
9786  // see above note
9787 
9788  for(int y = 0; y < TDEntry.NumberOfTrains; y++)
9789  {
9790  TTrainOperatingData &TTOD = TDEntry.TrainOperatingDataVector.at(y);
9791  if(TTOD.RunningEntry != NotStarted)
9792  {
9793  continue;
9794  }
9795 
9796 //Multiplayer: here check for a train entering at a coupling {RearStartOrRepeatMins shows if it's a coupling or not), and can only be a Snt entry
9797 //if so and no arrival signalled yet bypass the timetabled arrival
9798 //if so and arrival signalled then start the new service, using the repeat number and headcode for the entering train
9799 //if a repeat is skipped then should be ok if it arrives later as its RunningEntry is still NotStarted
9800 
9801  if(GetRepeatTime(2, AVEntry0.EventTime, y, IncrementalMinutes) > TTClockTime)
9802  {
9803  break; // all the rest will also be greater
9804  }
9805  AnsiString TrainHeadCode = GetRepeatHeadCode(22, TDEntry.HeadCode, y, IncrementalDigits);
9806  if(AddTrain(2, AVEntry0.RearStartOrRepeatMins, AVEntry0.FrontStartOrRepeatDigits, TrainHeadCode, TDEntry.StartSpeed, TDEntry.Mass,
9807  TDEntry.MaxRunningSpeed, TDEntry.MaxBrakeRate, TDEntry.PowerAtRail, "Timetable", &TDEntry, y, IncrementalMinutes, IncrementalDigits,
9808  TDEntry.SignallerSpeed, AVEntry0.SignallerControl, EventType))
9809  {
9810  TTOD.TrainID = TrainVector.back().TrainID;
9811  TTOD.RunningEntry = Running;
9812  TrainVector.back().Description = TDEntry.FixedDescription; //added at v2.16.1
9813  }
9814  else if(EventType == FailTrainEntry)
9815  {
9816  break; // if a train can't enter no point checking any more repeats as they won't be able to enter either
9817  }
9818  }
9819  }
9820  if(AVEntry0.Command == "Snt-sh")
9821  // just start this once, shuttle repeats take care of restarts
9822  {
9823  // calc below only for Snt & Snt-sh entries rather than all entries to save time
9824  const TActionVectorEntry &AVEntryLast = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
9825  int IncrementalMinutes = 0;
9826  int IncrementalDigits = 0;
9827  if(AVEntryLast.FormatType == Repeat)
9828  {
9829  IncrementalMinutes = AVEntryLast.RearStartOrRepeatMins;
9830  IncrementalDigits = AVEntryLast.FrontStartOrRepeatDigits;
9831  }
9832  if((AVEntryLast.FormatType == Repeat) && (TDEntry.NumberOfTrains < 2))
9833  {
9834  throw Exception("Error - Repeat entry && less than two trains for Snt-sh entry: " + TDEntry.HeadCode);
9835  }
9836  // see above note
9837  TTrainOperatingData &TTOD = TDEntry.TrainOperatingDataVector.at(0);
9838  if(TTOD.RunningEntry == NotStarted)
9839  {
9840  if(AVEntry0.EventTime <= TTClockTime)
9841  {
9842  if(AddTrain(3, AVEntry0.RearStartOrRepeatMins, AVEntry0.FrontStartOrRepeatDigits, TDEntry.HeadCode, TDEntry.StartSpeed, TDEntry.Mass,
9843  TDEntry.MaxRunningSpeed, TDEntry.MaxBrakeRate, TDEntry.PowerAtRail, "Timetable", &TDEntry, 0, IncrementalMinutes, IncrementalDigits,
9844  TDEntry.SignallerSpeed, false, EventType))
9845  // false for SignallerControl
9846  {
9847  TTOD.TrainID = TrainVector.back().TrainID;
9848  TTOD.RunningEntry = Running;
9849  TrainVector.back().Description = TDEntry.FixedDescription; //added at v2.16.1
9850  }
9851  else if(EventType == FailTrainEntry)
9852  {
9853  break; // if a train can't enter no point checking any more repeats as they won't be able to enter either
9854  }
9855  }
9856  }
9857  }
9858  }
9859  }
9860 
9861  // deal with running trains but abort if any vectors added, would probably be OK but don't risk a vector reallocation disrupting the
9862  // iteration, next cycle will catch up with any other pending updates
9863  if(!TrainVector.empty())
9864  {
9865  TrainAdded = false;
9866 
9867 //elapsed time investigations
9868 
9869 //elapsed time segment
9870 //double Start, End;
9871 //AnsiString ElapsedTimeReport = "";
9872 //end elasped time segment
9873  AllRoutes->CallonVector.clear(); // this will be rebuilt during the calls to UpdateTrain
9874 //elapsed time segment
9875 //PerfLogForm->PerformanceLog(-1, "\n Train vector size: " + AnsiString(TrainVector.size()) + '\n');
9876 //PerfLogForm->PerformanceLog(-1, "Start time list");
9877 //end elapsed time segment
9878  for(unsigned int x = 0; x < TrainVector.size(); x++)
9879  {
9880 //elapsed time segment
9881 //Start = double(GetTime()) * 86400; //secs
9882 //end elapsed time segment
9883  TrainVectorAt(33, x).UpdateTrain(0);
9884 //elapsed time segment
9885 //End = double(GetTime()) * 86400;
9886 //ElapsedTimeReport = TrainVectorAt(-1, x).TrainDataEntryPtr->ServiceReference + AnsiString(" ") + AnsiString(int((End - Start) * 1000)); //msecs
9887 //PerfLogForm->PerformanceLog(-1, ElapsedTimeReport);
9888 //end elapsed time segment
9889 
9890 //end elapsed time investigations
9891 
9892  /* added HasTrainGone() condition below in v0.4c to prevent 2 trains both having TrainGone set in UpdateTrain
9893  at the same time. That caused the error Craig Weekes reported in November 2010 where 2 trains exited at the same time, and later the TrainVector
9894  iterates in reverse to erase the second train to have gone (when the first train to have gone comes before the second in TrainVector),
9895  but afterwards ReplotTrains iterates forwards and therefore replots the first train to have gone and therefore sets the TrainIDOnElement value
9896  to the exited train, with nothing to reset it. Hovering the mouse over that element with train information enabled causes an error because
9897  the track element thinks the train is still there, whereas it is missing from the TrainVector. BUT subsequently (in v2.11.1) changed RePlotTrains
9898  so it doesn't plot trains with TrainGone set, but left this is as does no harm
9899 
9900  Had another error notified by Kevin Smith on 02/01/22 where a train was manually removed in the same clock cycle as a train exited, and this caused
9901  the same error as above. Did a lot of experimenting but eventually cured it with two changes, first as above in RePlotTrains, and also below adding
9902  a break; command after one TrainHasGone() dealt with. There were introduced in v2.11.1 & seems ok now
9903 
9904  These changes should deal with any number of TrainGone flags set in the same clock cycle - from exiting, manual removal, or joins
9905  */
9906  if(TrainAdded || TrainVectorAt(35, x).HasTrainGone())
9907  {
9908  break; //only one exited train will be dealt with at a time (see below) so no point looking further
9909  }
9910  }
9911  // set warning flags (ManualLCDownAttentionWarning dealt with in InterfaceUnit)
9912  CrashWarning = false;
9913  DerailWarning = false;
9914  SPADWarning = false;
9915  CallOnWarning = false;
9916  SignalStopWarning = false;
9917  BufferAttentionWarning = false;
9918  TrainFailedWarning = false;
9919  for(int x = TrainVector.size() - 1; x >= 0; x--) // reverse because of erase
9920  {
9921  TTrain &Train = TrainVectorAt(34, x);
9922  if(Train.Crashed)
9923  // can't use background colours for crashed & derailed because same colour
9924  {
9925  CrashWarning = true;
9926  }
9927  else if(Train.Derailed)
9928  // can't use background colours for crashed & derailed because same colour
9929  {
9930  DerailWarning = true;
9931  }
9932  else if(Train.BackgroundColour == clSPADBackground)
9933  // use colour as that changes as soon as passes signal
9934  {
9935  SPADWarning = true;
9936  }
9937  else if(Train.BackgroundColour == clTrainFailedBackground)
9938  {
9939  TrainFailedWarning = true;
9940  }
9941  else if(Train.BackgroundColour == clCallOnBackground)
9942  // use colour as also stopped at signal
9943  {
9944  CallOnWarning = true;
9945  }
9946  else if(Train.BackgroundColour == clSignalStopBackground)
9947  // use colour to distinguish from call-on
9948  {
9949  SignalStopWarning = true;
9950  }
9951  else if(Train.BackgroundColour == clBufferAttentionNeeded)
9952  // use colour to distinguish from ordinary buffer stop
9953  {
9954  BufferAttentionWarning = true;
9955  }
9956  if(Train.HasTrainGone())
9957  {
9958  AnsiString Loc = "";
9959  bool ElementFound = false;
9960  TTrackElement TE;
9961  if(Train.LagElement > -1)
9962  {
9963  TE = Track->TrackElementAt(531, Train.LagElement);
9964  ElementFound = true;
9965  }
9966  else if(Train.MidElement > -1)
9967  {
9968  TE = Track->TrackElementAt(779, Train.MidElement);
9969  ElementFound = true;
9970  }
9971  else if(Train.LeadElement > -1)
9972  {
9973  TE = Track->TrackElementAt(780, Train.LeadElement);
9974  ElementFound = true;
9975  }
9976  if(ElementFound)
9977  {
9978  if(TE.ActiveTrackElementName != "")
9979  {
9980  Loc = TE.ActiveTrackElementName + ", track element " + TE.ElementID;
9981  }
9982  else
9983  {
9984  Loc = "track element " + TE.ElementID;
9985  }
9986  }
9987  TActionVectorEntry *AVEntryPtr = Train.ActionVectorEntryPtr;
9988  if((Train.SignallerRemoved) || (Train.JoinedOtherTrainFlag))
9989  // need above first because may also have ActionVectorEntryPtr == "Fer"
9990  {
9991  Train.UnplotTrain(9);
9992  // added at v1.3.0 to reset signals after train removed from an autosigsroute
9994  {
9997  }
9998  // end of addition
9999  AllRoutes->RebuildRailwayFlag = true;
10000  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode, to replot LCs
10001  // correctly after a crash
10002  }
10003  else if(AVEntryPtr->Command == "Fer")
10004  {
10005  bool CorrectExit = false;
10006  if(!AVEntryPtr->ExitList.empty())
10007  {
10008  for(TNumListIterator ELIT = AVEntryPtr->ExitList.begin(); ELIT != AVEntryPtr->ExitList.end(); ELIT++)
10009  {
10010  if(*ELIT == Train.LagElement)
10011  {
10012  CorrectExit = true;
10013  }
10014  }
10015  }
10016  if(CorrectExit)
10017  {
10018  Train.LogAction(19, Train.HeadCode, "", Leave, Loc, "", AVEntryPtr->EventTime, AVEntryPtr->Warning);
10019  }
10020  else
10021  {
10022  LogActionError(38, Train.HeadCode, "", FailIncorrectExit, Loc);
10023  }
10024  }
10025  else
10026  {
10027  if(!AVEntryPtr->SignallerControl)
10028  {
10029  LogActionError(26, Train.HeadCode, "", FailUnexpectedExitRailway, Loc);
10030  Train.SendMissedActionLogs(2, -2, AVEntryPtr);
10031  // -2 is marker for send messages for all remaining actions except Fer if present
10032  }
10033  else
10034  {
10035  Train.LogAction(31, Train.HeadCode, "", SignallerLeave, Loc, "", TDateTime(0), false); // false for Warning
10036  }
10037  }
10038  Utilities->CumulativeDelayedRandMinsAllTrains += Train.CumulativeDelayedRandMinsOneTrain; //added at v2.13.0 for random delays
10039  Train.TrainDataEntryPtr->TrainOperatingDataVector.at(Train.RepeatNumber).RunningEntry = Exited;
10040  Train.DeleteTrain(1);
10041  TrainVector.erase(TrainVector.begin() + x);
10042  ReplotTrains(1, Display); //to reset ElementIDs for remaining trains when have removed a train
10043  //NB: won't plot any trains with TrainGone flag set (changed at v2.11.1)
10044  break; //added at v2.11.1 to ensure that only one train with TrainGone set is dealt with in one clock cycle
10045  }
10046  }
10047  }
10048  else
10049  {
10050  // reset all flags in case last train removed with flag set
10051  CrashWarning = false;
10052  DerailWarning = false;
10053  SPADWarning = false;
10054  CallOnWarning = false;
10055  SignalStopWarning = false;
10056  BufferAttentionWarning = false;
10057  TrainFailedWarning = false;
10058  }
10059  // update OpTimeToActMultimap
10061  {
10063  // clears entries then adds values for running trains then for continuation entries
10065  //added for multiplayer for running trains only
10066  }
10067  Utilities->Clock2Stopped = ClockState;
10068  Utilities->CallLogPop(723);
10069 }
10070 
10071 // ---------------------------------------------------------------------------
10073 {
10074  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",FinishedOperation");
10075  if(!TrainVector.empty())
10076  {
10077  for(int x = TrainVector.size() - 1; x >= 0; x--)
10078  {
10079  TrainVectorAt(50, x).DeleteTrain(2);
10080  }
10081  TrainVector.clear();
10082  }
10083  if(!TrainDataVector.empty())
10084  {
10085  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
10086  {
10087  TTrainDataEntry &TDEntry = TrainDataVector.at(x);
10088  for(int y = 0; y < TDEntry.NumberOfTrains; y++)
10089  {
10090  TTrainOperatingData &TOD = TDEntry.TrainOperatingDataVector.at(y);
10091  TOD.RunningEntry = NotStarted;
10092  TOD.TrainID = -1;
10093  TOD.EventReported = NoEvent;
10094  }
10095  }
10096  }
10097  Display->GetOutputLog1()->Caption = "";
10098  Display->GetOutputLog2()->Caption = "";
10099  Display->GetOutputLog3()->Caption = "";
10100  Display->GetOutputLog4()->Caption = "";
10101  Display->GetOutputLog5()->Caption = "";
10102  Display->GetOutputLog6()->Caption = "";
10103  Display->GetOutputLog7()->Caption = "";
10104  Display->GetOutputLog8()->Caption = "";
10105  Display->GetOutputLog9()->Caption = "";
10106  Display->GetOutputLog10()->Caption = "";
10107  Utilities->CallLogPop(1352);
10108 }
10109 
10110 // ---------------------------------------------------------------------------
10111 
10113 {
10114  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ReplotTrains");
10115  if(!TrainVector.empty())
10116  {
10117  for(unsigned int x = 0; x < TrainVector.size(); x++)
10118  {
10119  if(!TrainVectorAt(84, x).HasTrainGone()) //added at v2.11.0 to prevent plotting a train pending removal & particularly to prevent TrainElementID's being reinstated
10120  { //see Kevin Smith error information for details
10121  TrainVectorAt(51, x).PlotTrain(4, Disp);
10122  }
10123  }
10124  }
10125  Utilities->CallLogPop(724);
10126 }
10127 
10128 // ---------------------------------------------------------------------------
10129 
10130 void TTrainController::WriteTrainsToImage(int Caller, Graphics::TBitmap *Bitmap)
10131 {
10132  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",WriteTrainsToImage");
10133  if(!TrainVector.empty())
10134  {
10135  for(unsigned int x = 0; x < TrainVector.size(); x++)
10136  {
10137  TrainVectorAt(61, x).WriteTrainToImage(0, Bitmap);
10138  }
10139  }
10140  Utilities->CallLogPop(1707);
10141 }
10142 
10143 // ---------------------------------------------------------------------------
10144 
10146 {
10147  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",UnplotTrains");
10148  if(!TrainVector.empty())
10149  {
10150  for(unsigned int x = 0; x < TrainVector.size(); x++)
10151  {
10152  TrainVectorAt(52, x).UnplotTrain(10);
10153  }
10154  }
10156  Utilities->CallLogPop(725);
10157 }
10158 
10159 // ---------------------------------------------------------------------------
10160 
10161 bool TTrainController::AddTrain(int Caller, int RearPosition, int FrontPosition, AnsiString HeadCode, int StartSpeed, int Mass, double MaxRunningSpeed,
10162  double MaxBrakeRate, double PowerAtRail, AnsiString ModeStr, TTrainDataEntry *TrainDataEntryPtr, int RepeatNumber, int IncrementalMinutes,
10163  int IncrementalDigits, int SignallerSpeed, bool SignallerControl, TActionEventType &EventType)
10164 {
10165  LogEvent(AnsiString(Caller) + ",AddTrain," + AnsiString(RearPosition) + "," + AnsiString(FrontPosition) + "," + HeadCode + "," + AnsiString(StartSpeed) +
10166  "," + AnsiString(Mass) + "," + ModeStr);
10167  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",AddTrain," + AnsiString(RearPosition) + "," + AnsiString(FrontPosition) +
10168  "," + HeadCode + "," + AnsiString(StartSpeed) + "," + AnsiString(Mass) + "," + ModeStr); //at v2.11.1 dropped later headcode - was listed twice
10169 
10170  int RearExitPos = -1;
10171 
10172  for(int x = 0; x < 4; x++)
10173  {
10174  if(Track->TrackElementAt(519, RearPosition).Conn[x] == FrontPosition)
10175  {
10176  RearExitPos = x;
10177  }
10178  }
10179  if(RearExitPos == -1)
10180  {
10181  throw Exception("Error, RearExit == -1 in AddTrain");
10182  }
10183  bool ReportFlag = true;
10184 
10185  // used to stop repeated messages from CheckStartAllowable when split failed
10186  if(TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).EventReported != NoEvent)
10187  {
10188  ReportFlag = false;
10189  }
10190  if(!CheckStartAllowable(0, RearPosition, RearExitPos, HeadCode, ReportFlag, EventType))
10191  {
10192  // messages sent to performance log in CheckStartAllowable if ReportFlag true
10193  TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).EventReported = EventType;
10194  Utilities->CallLogPop(938);
10195  return(false);
10196  }
10197  TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).EventReported = NoEvent;
10198  TTrainMode TrainMode = NoMode;
10199 
10200  if(ModeStr == "Timetable")
10201  {
10202  TrainMode = Timetable;
10203  }
10204  // all else gives 'None', 'Signaller' set within program
10205 
10206  if(MaxRunningSpeed < 10)
10207  {
10208  MaxRunningSpeed = 10; // added at v0.6 to avoid low max speeds
10209  }
10210  if(SignallerSpeed < 10)
10211  {
10212  SignallerSpeed = 10; // added at v0.6 to avoid low max speeds
10213  }
10214  TTrain *NewTrain = new TTrain(0, RearPosition, RearExitPos, HeadCode, StartSpeed, Mass, MaxRunningSpeed, MaxBrakeRate, PowerAtRail, TrainMode,
10215  TrainDataEntryPtr, RepeatNumber, IncrementalMinutes, IncrementalDigits, SignallerSpeed);
10216 
10217  LogEvent("AddTrainSupplemental: Service Ref = " + TrainDataEntryPtr->ServiceReference + ", TrainID = " + AnsiString(NewTrain->TrainID)); //new at v2.11.1 so can relate headcode to ID
10218 
10219  NewTrain->ActionVectorEntryPtr = &(TrainDataEntryPtr->ActionVector.at(0));
10220  // initialise here rather than in TTrain constructor as create trains
10221  // with Null TrainDataEntryPtr when loading session trains
10222  if(SignallerControl)
10223  {
10224  NewTrain->TimetableFinished = true;
10225  NewTrain->SignallerStoppingFlag = false;
10226  NewTrain->TrainMode = Signaller;
10227  if(NewTrain->MaxRunningSpeed > NewTrain->SignallerMaxSpeed)
10228  {
10229  NewTrain->MaxRunningSpeed = NewTrain->SignallerMaxSpeed;
10230  }
10232  }
10233  // deal with starting conditions:-
10234  // unlocated Snt: just report entry & advance pointer
10235  // located Snt or Sfs: set station conditions as would if had reached stop point in Update(), & advance the ActionVectorEntryPtr
10236  // Sns doesn't need a new train
10237  if(NewTrain->ActionVectorEntryPtr->LocationName != "")
10238  // covers all above located starts
10239  // if location of Snt was a station (that is set as LocationName, i.e. not just any station) that isn't next departure station then
10240  // wouldn't have accepted the timetable
10241  {
10242  // first check if LeadElement (can't access LeadElement directly yet as not set, use FrontPosition instead) is buffers, note that
10243  // StoppedAtBuffers is set in UpdateTrain()
10244  if(Track->TrackElementAt(520, FrontPosition).TrackType == Buffers)
10245  // buffer end must be ahead of train or would have failed start position check
10246  {
10247  NewTrain->StoppedAtLocation = true;
10248  NewTrain->PlotStartPosition(0);
10250  NewTrain->LogAction(20, NewTrain->HeadCode, "", Create, NewTrain->ActionVectorEntryPtr->LocationName, "", NewTrain->ActionVectorEntryPtr->EventTime,
10251  NewTrain->ActionVectorEntryPtr->Warning);
10252  if(!SignallerControl) // don't advance if SignalControlEntry
10253  {
10254  NewTrain->ActionVectorEntryPtr++;
10255  // should be a command, could be a location departure but if so can't depart so set 'Hold' anyway
10256  }
10257  NewTrain->LastActionTime = TTClockTime;
10258  }
10259  // else a through station stop
10260  else
10261  {
10262  NewTrain->StoppedAtLocation = true;
10263  NewTrain->PlotStartPosition(10);
10265  NewTrain->LogAction(21, NewTrain->HeadCode, "", Create, NewTrain->ActionVectorEntryPtr->LocationName, "", NewTrain->ActionVectorEntryPtr->EventTime,
10266  NewTrain->ActionVectorEntryPtr->Warning);
10267  if(!SignallerControl) // don't advance if SignalControlEntry
10268  {
10269  NewTrain->ActionVectorEntryPtr++;
10270  }
10271  NewTrain->LastActionTime = TTClockTime;
10272  }
10273  }
10274  else // unlocated entry (i.e. not a stop entry, but could still be at a named location)
10275  {
10276  NewTrain->PlotStartPosition(11);
10277  TTrackElement TE = Track->TrackElementAt(530, NewTrain->RearStartElement);
10278  AnsiString Loc = "";
10279  if(TE.ActiveTrackElementName != "")
10280  {
10281  Loc = TE.ActiveTrackElementName + ", track element " + TE.ElementID;
10282  }
10283  else
10284  {
10285  Loc = "track element " + TE.ElementID;
10286  }
10287  if(TE.TrackType == Continuation)
10288  {
10289  NewTrain->LogAction(22, NewTrain->HeadCode, "", Enter, Loc, "", NewTrain->ActionVectorEntryPtr->EventTime, NewTrain->ActionVectorEntryPtr->Warning);
10290  }
10291  else
10292  {
10293  NewTrain->LogAction(23, NewTrain->HeadCode, "", Create, Loc, "", NewTrain->ActionVectorEntryPtr->EventTime, NewTrain->ActionVectorEntryPtr->Warning);
10294  }
10295  if(!SignallerControl) // don't advance if SignalControlEntry
10296  {
10297  NewTrain->ActionVectorEntryPtr++;
10298  }
10299  NewTrain->LastActionTime = TTClockTime;
10300  // no need to set LastActionTime for an unlocated entry
10301  }
10302  // cancel a wrong-direction route if either element of train starts on one
10303  if(NewTrain->LeadElement > -1)
10304  {
10305  NewTrain->CheckAndCancelRouteForWrongEndEntry(3, NewTrain->LeadElement, NewTrain->LeadEntryPos);
10306  }
10307  if(NewTrain->MidElement > -1)
10308  {
10309  NewTrain->CheckAndCancelRouteForWrongEndEntry(4, NewTrain->MidElement, NewTrain->MidEntryPos);
10310  }
10311  // set signals for a right-direction autosigs route for either element of train on one
10312  // erase elements back to start for a non-autosigs route & check if an autosigs route immediately behind it, and if so set its signals
10313  // note that all but autosigs routes become part of a single route, so there can only be an autosigs route behind the non-autosigs route
10314  int RouteNumber = -1;
10315  bool SignalsSet = false;
10316 
10317  if(NewTrain->LeadElement > -1)
10318  {
10319  if(AllRoutes->GetRouteTypeAndNumber(13, NewTrain->LeadElement, NewTrain->LeadEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute)
10320  {
10321  // below added in place of SetRouteSignals in v2.4.0 as don't want to set signals from start of route for a new train addition
10322  int RouteStartPosition;
10323  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
10324  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(21, Track->TrackElementAt(955, FrontPosition).HLoc,
10325  Track->TrackElementAt(956, FrontPosition).VLoc, SecondPair);
10326  if(FirstPair.first == RouteNumber)
10327  {
10328  RouteStartPosition = FirstPair.second;
10329  }
10330  else if(SecondPair.first == RouteNumber)
10331  {
10332  RouteStartPosition = SecondPair.second;
10333  }
10334  else
10335  {
10336  throw Exception("Error, RouteNumber not found in Route2MultiMap in 1st of 2 calls to SetAllRearwardsSignals in AddTrain");
10337  }
10338  AllRoutes->SetAllRearwardsSignals(10, 0, RouteNumber, RouteStartPosition);
10339  SignalsSet = true;
10340  // AllRoutes->GetFixedRouteAt(, RouteNumber).SetRouteSignals(); above substituted in v2.4.0
10341  }
10342  else if(RouteNumber > -1) // non-autosigsroute
10343  {
10344  TPrefDirElement TempPDE = AllRoutes->GetFixedRouteAt(181, RouteNumber).GetFixedPrefDirElementAt(194, 0);
10345  int FirstTVPos = TempPDE.GetTrackVectorPosition();
10346  int FirstELinkPos = TempPDE.GetELinkPos();
10347  while(TempPDE.GetTrackVectorPosition() != (unsigned int)(NewTrain->LeadElement))
10348  {
10349  AllRoutes->RemoveRouteElement(16, TempPDE.HLoc, TempPDE.VLoc, TempPDE.GetELink());
10350  TempPDE = AllRoutes->GetFixedRouteAt(182, RouteNumber).GetFixedPrefDirElementAt(195, 0);
10351  }
10352  if(TempPDE.GetTrackVectorPosition() == (unsigned int)(NewTrain->LeadElement))
10353  {
10354  AllRoutes->RemoveRouteElement(17, TempPDE.HLoc, TempPDE.VLoc, TempPDE.GetELink());
10355  // remove the last element under LeadElement
10356  }
10357  AllRoutes->RebuildRailwayFlag = true;
10358  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode
10359  // now deal with a rear linked autosigs route
10360  if(Track->TrackElementAt(820, FirstTVPos).Conn[FirstELinkPos] > -1)
10361  {
10362  int LinkedRouteNumber = -1;
10363  if(AllRoutes->GetRouteTypeAndNumber(17, Track->TrackElementAt(821, FirstTVPos).Conn[FirstELinkPos],
10364  Track->TrackElementAt(822, FirstTVPos).ConnLinkPos[FirstELinkPos], LinkedRouteNumber) == TAllRoutes::AutoSigsRoute)
10365  {
10366  AllRoutes->GetFixedRouteAt(169, LinkedRouteNumber).SetRouteSignals(0);
10367  // this is ok as here we are setting signals from the start of the route
10368  }
10369  }
10370  SignalsSet = true;
10371  }
10372  }
10373  if(NewTrain->MidElement > -1)
10374  // if entering at a continuation MidElement == -1
10375  {
10376  // this is included in case a train starts with LeadElement on no route and MidElement on a route
10377  if(!SignalsSet)
10378  {
10379  RouteNumber = -1;
10380  if(AllRoutes->GetRouteTypeAndNumber(14, NewTrain->MidElement, NewTrain->MidEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute)
10381  {
10382  // below added in place of SetRouteSignals in v2.4.0 as don't want to set signals from start of route for a new train addition
10383  int RouteStartPosition;
10384  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
10385  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(22, Track->TrackElementAt(957, RearPosition).HLoc,
10386  Track->TrackElementAt(958, RearPosition).VLoc, SecondPair);
10387  if(FirstPair.first == RouteNumber)
10388  {
10389  RouteStartPosition = FirstPair.second;
10390  }
10391  else if(SecondPair.first == RouteNumber)
10392  {
10393  RouteStartPosition = SecondPair.second;
10394  }
10395  else
10396  {
10397  throw Exception("Error, RouteNumber not found in Route2MultiMap in 2nd of 2 calls to SetAllRearwardsSignals in AddTrain");
10398  }
10399  AllRoutes->SetAllRearwardsSignals(11, 0, RouteNumber, RouteStartPosition);
10400  SignalsSet = true;
10401  // AllRoutes->GetFixedRouteAt(, RouteNumber).SetRouteSignals(); above substituted in v2.4.0
10402  }
10403  else if(RouteNumber > -1) // non-autosigsroute
10404  {
10405  TPrefDirElement TempPDE = AllRoutes->GetFixedRouteAt(184, RouteNumber).GetFixedPrefDirElementAt(196, 0);
10406  int FirstTVPos = TempPDE.GetTrackVectorPosition();
10407  int FirstELinkPos = TempPDE.GetELinkPos();
10408  while(TempPDE.GetTrackVectorPosition() != (unsigned int)(NewTrain->MidElement))
10409  {
10410  AllRoutes->RemoveRouteElement(18, TempPDE.HLoc, TempPDE.VLoc, TempPDE.GetELink());
10411  TempPDE = AllRoutes->GetFixedRouteAt(185, RouteNumber).GetFixedPrefDirElementAt(197, 0);
10412  }
10413  if(TempPDE.GetTrackVectorPosition() == (unsigned int)(NewTrain->MidElement))
10414  {
10415  AllRoutes->RemoveRouteElement(19, TempPDE.HLoc, TempPDE.VLoc, TempPDE.GetELink());
10416  // remove the last element under LeadElement
10417  }
10418  AllRoutes->RebuildRailwayFlag = true;
10419  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode
10420  // now deal with a rear linked autosigs route
10421  if(Track->TrackElementAt(823, FirstTVPos).Conn[FirstELinkPos] > -1)
10422  {
10423  int LinkedRouteNumber = -1;
10424  if(AllRoutes->GetRouteTypeAndNumber(19, Track->TrackElementAt(824, FirstTVPos).Conn[FirstELinkPos],
10425  Track->TrackElementAt(825, FirstTVPos).ConnLinkPos[FirstELinkPos], LinkedRouteNumber) == TAllRoutes::AutoSigsRoute)
10426  {
10427  AllRoutes->GetFixedRouteAt(170, LinkedRouteNumber).SetRouteSignals(1);
10428  // this is ok as now we are setting signals from the start of the route
10429  }
10430  }
10431  }
10432  }
10433  }
10434  TrainVector.push_back(*NewTrain);
10435  Utilities->CallLogPop(731);
10436  return(true);
10437 }
10438 
10439 // ---------------------------------------------------------------------------
10440 
10441 int TTrainController::EntryPos(int Caller, int TrainIDIn, int TrackVectorNumber)
10442 {
10443  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",EntryPos," + AnsiString(TrainIDIn) + "," +
10444  AnsiString(TrackVectorNumber));
10445  int VecPos = -1;
10446 
10447  for(unsigned int x = 0; x < TrainVector.size(); x++)
10448  {
10449  if(TrainVectorAt(1, x).TrainID == TrainIDIn)
10450  {
10451  VecPos = x;
10452  }
10453  }
10454  if(VecPos == -1)
10455  {
10456  throw Exception("Error, VecPos not set in EntryPos");
10457  }
10458  if(TrainVectorAt(2, VecPos).LeadElement == TrackVectorNumber)
10459  {
10460  Utilities->CallLogPop(734);
10461  return(TrainVectorAt(3, VecPos).LeadEntryPos);
10462  }
10463  else if(TrainVectorAt(4, VecPos).MidElement == TrackVectorNumber)
10464  {
10465  Utilities->CallLogPop(735);
10466  return(TrainVectorAt(5, VecPos).MidEntryPos);
10467  }
10468  else if(TrainVectorAt(6, VecPos).LagElement == TrackVectorNumber)
10469  {
10470  Utilities->CallLogPop(736);
10471  return(TrainVectorAt(7, VecPos).LagEntryPos);
10472  }
10473  Utilities->CallLogPop(737);
10474  return(-1);
10475 }
10476 
10477 // ---------------------------------------------------------------------------
10478 
10480 {
10481  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainVectorAtIdent," + AnsiString(TrainID));
10482  for(unsigned int x = 0; x < TrainVector.size(); x++)
10483  {
10484  if(TrainVectorAt(53, x).TrainID == TrainID)
10485  {
10486  Utilities->CallLogPop(738);
10487  return(TrainVectorAt(54, x));
10488  }
10489  }
10490  throw Exception("Error - No Train identified in TrainVectorAtIdent with ID = " + AnsiString(TrainID));
10491 }
10492 
10493 // ---------------------------------------------------------------------------
10494 
10495 bool TTrainController::TrainExistsAtIdent(int Caller, int TrainID)
10496 // return true if find the train (added at v2.4.0 as can select a removed train in
10497 // ActionsDueListBox before it updates - reported by LiWinDom in error report 23/04/20)
10498 {
10499  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainExistsAtIdent," + AnsiString(TrainID));
10500  for(unsigned int x = 0; x < TrainVector.size(); x++)
10501  {
10502  if(TrainVectorAt(69, x).TrainID == TrainID)
10503  {
10504  Utilities->CallLogPop(2152);
10505  return(true);
10506  }
10507  }
10508  Utilities->CallLogPop(2153);
10509  return(false);
10510 }
10511 
10512 // ---------------------------------------------------------------------------
10513 
10514 TDateTime TTrainController::GetControllerTrainTime(int Caller, TDateTime Time, int RepeatNumber, int IncrementalMinutes)
10515 {
10516  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetControllerTrainTime," + AnsiString(RepeatNumber) + "," +
10517  Utilities->Format96HHMMSS(Time));
10518  TDateTime RepeatTime = TrainController->GetRepeatTime(47, Time, RepeatNumber, IncrementalMinutes);
10519 
10520  Utilities->CallLogPop(2061);
10521  return(RepeatTime);
10522 }
10523 
10524 // ---------------------------------------------------------------------------
10525 
10526 AnsiString TTrainController::ContinuationEntryFloatingTTString(int Caller, TTrainDataEntry *TTDEPtr, int RepNum, int IncMins, int IncDig)
10527 // Enter with Ptr pointing to first action to be listed (i.e. next action)
10528 {
10529  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ContinuationEntryFloatingTTString" + "," + TTDEPtr->HeadCode);
10530  AnsiString RetStr = "", PartStr = "";
10531  int Count = 0;
10532  TActionVectorIterator Ptr = TTDEPtr->ActionVector.begin();
10533 
10534  Ptr--; // because incremented at start of loop
10535  do
10536  {
10537  Ptr++;
10538  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
10539  {
10540  continue; // move past the starting entry
10541  }
10542  if((Ptr->FormatType == Repeat) || Ptr >= TTDEPtr->ActionVector.end())
10543  {
10544  break;
10545  }
10546  if(Ptr->SignallerControl)
10547  {
10548  RetStr = "Train under signaller control";
10549  break;
10550  }
10551  if(Ptr->FormatType == TimeTimeLoc)
10552  {
10553  if(Ptr->ArrivalTime == Ptr->DepartureTime)
10554  {
10555  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(0, Ptr->ArrivalTime, RepNum, IncMins)) + ": Arrive & depart from " + Ptr->LocationName;
10556  }
10557  else
10558  {
10559  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(1, Ptr->ArrivalTime, RepNum, IncMins)) + ": Arrive at " + Ptr->LocationName + '\n' +
10560  Utilities->Format96HHMM(GetControllerTrainTime(2, Ptr->DepartureTime, RepNum, IncMins)) + ": Depart from " + Ptr->LocationName;
10561  Count++; // because there are 2 entries
10562  }
10563  }
10564  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
10565  {
10566  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(3, Ptr->ArrivalTime, RepNum, IncMins)) + ": Arrive at " + Ptr->LocationName;
10567  }
10568  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
10569  {
10570  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(4, Ptr->DepartureTime, RepNum, IncMins)) + ": Depart from " + Ptr->LocationName;
10571  }
10572  else if(Ptr->FormatType == PassTime) // new
10573  {
10574  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(5, Ptr->EventTime, RepNum, IncMins)) + ": Pass " + Ptr->LocationName;
10575  }
10576  else if(Ptr->Command == "Fns")
10577  {
10578  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(6, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
10579  TrainController->GetRepeatHeadCode(46, Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
10580  PartStr = ControllerGetNewServiceDepartureInfo(11, Ptr, RepNum, TTDEPtr, Ptr->LinkedTrainEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to PartStr
10581  }
10582  else if(Ptr->Command == "F-nshs")
10583  {
10584  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(7, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
10585  Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName;
10586  PartStr = ControllerGetNewServiceDepartureInfo(13, Ptr, 0, TTDEPtr, Ptr->LinkedTrainEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to RetStr
10587  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
10588  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
10589  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
10590  }
10591 //Since this is a new continuation entry service it can't be Fns-sh or Frh-sh but leave these in for consistency with TTrain::FloatingTimetableString
10592  else if((Ptr->Command == "Fns-sh") && (RepNum < (TTDEPtr->NumberOfTrains - 1))) // not the last repeat number
10593  {
10594  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(8, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
10595  TrainController->GetRepeatHeadCode(47, Ptr->OtherHeadCode, RepNum + 1, IncDig) + " at " + Ptr->LocationName;
10596  // use RepNum+1 because it's the repeat number of the NEXT shuttle service that is relevant
10597  PartStr = ControllerGetNewServiceDepartureInfo(15, Ptr, RepNum + 1, TTDEPtr, Ptr->LinkedTrainEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to RetStr
10598  }
10599  else if((Ptr->Command == "Fns-sh") && (RepNum >= (TTDEPtr->NumberOfTrains - 1))) // last repeat number
10600  {
10601  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(9, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
10602  Ptr->NonRepeatingShuttleLinkHeadCode, +" at " + Ptr->LocationName;
10603  PartStr = ControllerGetNewServiceDepartureInfo(17, Ptr, 0, TTDEPtr, Ptr->NonRepeatingShuttleLinkEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to RetStr
10604  }
10605  else if((Ptr->Command == "Frh-sh") && (RepNum < (TTDEPtr->NumberOfTrains - 1))) // not the last repeat number
10606  {
10607  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(10, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
10608  TrainController->GetRepeatHeadCode(48, Ptr->OtherHeadCode, RepNum + 1, IncDig) + " at " + Ptr->LocationName;
10609  // use RepNum+1 because it's the repeat number of the NEXT shuttle service that is relevant
10610  PartStr = ControllerGetNewServiceDepartureInfo(19, Ptr, RepNum + 1, TTDEPtr, Ptr->LinkedTrainEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to RetStr
10611  }
10612  else if((Ptr->Command == "Frh-sh") && (RepNum >= (TTDEPtr->NumberOfTrains - 1))) // last repeat number
10613  {
10614  PartStr = "Terminate at " + Ptr->LocationName;
10615  }
10616  else if(Ptr->Command == "Frh")
10617  {
10618  PartStr = "Terminate at " + Ptr->LocationName;
10619  }
10620  else if(Ptr->Command == "Fer")
10621  {
10622  AnsiString AllowedExits;
10623  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(11, Ptr->EventTime, RepNum, IncMins)) + ": Exit railway" +
10624  TrainController->GetExitLocationAndAt(3, Ptr->ExitList, AllowedExits) + AllowedExits;
10625  }
10626  else if(Ptr->Command == "Fjo")
10627  {
10628  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(12, Ptr->EventTime, RepNum, IncMins)) + ": Join " + TrainController->GetRepeatHeadCode(49,
10629  Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
10630  }
10631  else if(Ptr->Command == "jbo")
10632  {
10633  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(13, Ptr->EventTime, RepNum, IncMins)) + ": Joined by " + TrainController->GetRepeatHeadCode
10634  (50, Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
10635  }
10636  else if(Ptr->Command == "fsp")
10637  {
10638  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(14, Ptr->EventTime, RepNum, IncMins)) + ": Front split to " +
10639  TrainController->GetRepeatHeadCode(51, Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
10640  }
10641  else if(Ptr->Command == "rsp")
10642  {
10643  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(15, Ptr->EventTime, RepNum, IncMins)) + ": Rear split to " +
10644  TrainController->GetRepeatHeadCode(52, Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
10645  }
10646  else if(Ptr->Command == "cdt")
10647  {
10648  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(16, Ptr->EventTime, RepNum, IncMins)) + ": Change direction at " + Ptr->LocationName;
10649  }
10650  else if(Ptr->Command == "dsc")
10651  {
10652  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(27, Ptr->EventTime, RepNum, IncMins)) + ": Change description at " + Ptr->LocationName;
10653  }
10654  if(RetStr != "")
10655  {
10656  RetStr = RetStr + '\n' + PartStr;
10657  }
10658  else
10659  {
10660  RetStr = PartStr;
10661  }
10662  Count++;
10663  }
10664  while(Ptr < TTDEPtr->ActionVector.end() && (Count < 33) && ((Ptr->Command == "") || ((Ptr->Command != "") && (Ptr->Command[1] != 'F'))));
10665  // limit of 33 allows a max of 34 entries (may have gone from 32 to 34 because of a TimeTimeLoc), which with track and train status gives
10666  // a max of 48 lines, at 13 pixels each, = 624 pixels & screen height has 641 so will fit comfortably. Also 34 timetable entries is as far
10667  // forward as anyone should wish to see without looking at the full timetable
10668  Utilities->CallLogPop(2072);
10669  return(RetStr);
10670 }
10671 
10672 // ---------------------------------------------------------------------------
10673 
10675  TTrainDataEntry *LinkedTrainDataPtr, int IncrementalMinutes, int IncrementalDigits, AnsiString RetStr)
10676 { //no delays as train not entered yet //above added IncrementalDigits at v2.18.0 for GetRepeatHeadCode
10677  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - TDEPtr->ActionVector.begin()) + ","
10678  + AnsiString(RptNum) + ",ControllerGetNewServiceDepartureInfo," + TDEPtr->HeadCode);
10679  AnsiString DepTime = "", EventTime = "";
10680  bool CDTFlag = false; //reports if train changes direction before departs
10681  TActionVector NewServiceAV = LinkedTrainDataPtr->ActionVector;
10682  AnsiString CurrentLocation = NewServiceAV.at(0).LocationName; //added at v2.12.0 to show departure direction
10683  AnsiString TowardsLocation = ""; //added at v2.12.0 to show departure direction
10684  for(TActionVectorIterator AVI = NewServiceAV.begin(); AVI < NewServiceAV.end(); AVI++) //added at v2.12.0 to obtain departure direction
10685  {
10686  if((AVI->LocationName != CurrentLocation) && (AVI->LocationName != "") && (TowardsLocation == ""))
10687  {
10688  TowardsLocation = AVI->LocationName;
10689  }
10690  else if((AVI->Command == "Fer") && (TowardsLocation == "") && !AVI->ExitList.empty())
10691  {
10692  TTrackElement TE = Track->TrackElementAt(1453, (AVI->ExitList.front()));
10693  if(TE.ActiveTrackElementName != "")
10694  {
10695  TowardsLocation = TE.ActiveTrackElementName;
10696  }
10697  else
10698  {
10699  TowardsLocation = AnsiString("track element ID ") + TE.ElementID;
10700  }
10701  }
10702  }
10703  for(TActionVectorIterator AVI = NewServiceAV.begin(); AVI < NewServiceAV.end(); AVI++)
10704  {
10705  if(AVI->Command == "cdt")
10706  {
10707  CDTFlag = !CDTFlag; //toggles flag - allows for there being more than one cdt before departure
10708  continue;
10709  }
10710  if((AVI->Command == "fsp") || (AVI->Command == "rsp"))
10711  {
10712  EventTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(21, AVI->EventTime, RptNum, IncrementalMinutes));
10713  RetStr += "\nNew service splits at " + EventTime;
10714  Utilities->CallLogPop(2237);
10715  return(RetStr);
10716  }
10717  if(AVI->Command == "jbo")
10718  {
10719  EventTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(30, AVI->EventTime, RptNum, IncrementalMinutes));
10720  RetStr += "\nNew service joined by " + GetRepeatHeadCode(70, AVI->OtherHeadCode, RptNum, IncrementalDigits) + " at " + EventTime;
10721  Utilities->CallLogPop(2238); //above added GetRepeatHeadCode at v2.18.0 (was just AVI->OtherHeadCode before)
10722  return(RetStr);
10723  }
10724  if((AVI->Command == "Fns") || (AVI->Command == "F-nshs") || (AVI->Command == "Fns-sh"))
10725  {
10726  EventTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(31, AVI->EventTime, RptNum, IncrementalMinutes));
10727  RetStr += "\nNew service finishes and forms another new service at " + EventTime;
10728  Utilities->CallLogPop(2607);
10729  return(RetStr);
10730  }
10731  if(AVI->Command == "Fjo")
10732  {
10733  EventTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(22, AVI->EventTime, RptNum, IncrementalMinutes));
10734  RetStr += "\nNew service finishes and joins " + TrainController->GetRepeatHeadCode(71, AVI->OtherHeadCode, RptNum, IncrementalDigits) + " at " + EventTime;
10735  Utilities->CallLogPop(2608); //above added GetRepeatHeadCode at v2.18.0 (was just AVI->OtherHeadCode before)
10736  return(RetStr);
10737  }
10738  if(AVI->Command == "Frh")
10739  {
10740  RetStr += "\nNew service finishes and remains at location.";
10741  Utilities->CallLogPop(2609);
10742  return(RetStr);
10743  }
10744  if((AVI->FormatType == TimeLoc) && (AVI->DepartureTime > TDateTime(-1))) //departure time set
10745  {
10746  DepTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(23, AVI->DepartureTime, RptNum, IncrementalMinutes));
10747  if(CDTFlag)
10748  {
10749  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
10750  {
10751  RetStr += "\nNew service changes direction then departs towards " + TowardsLocation + " at " + DepTime;
10752  }
10753  else
10754  {
10755  RetStr += "\nNew service changes direction then departs at " + DepTime;
10756  }
10757  }
10758  else
10759  {
10760  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
10761  {
10762  RetStr += "\nNew service departs towards " + TowardsLocation + " at " + DepTime;
10763  }
10764  else
10765  {
10766  RetStr += "\nNew service departs at " + DepTime;
10767  }
10768  }
10769  Utilities->CallLogPop(2239);
10770  return(RetStr);
10771  }
10772  }
10773  Utilities->CallLogPop(2223);
10774  return(RetStr);
10775 }
10776 
10777 // ---------------------------------------------------------------------------
10778 // $$$$$$$$$$$$$$$$$$$$$$ Start of Timetable Functions $$$$$$$$$$$$$$$$$$$$$$$$
10779 /*
10780  Note: The terms 'action' and 'entry' have been used freely for individual code lines within services in comments & in variable names, but
10781  for messages and in the manual and help files the term Entry is reserved for a complete service or train (i.e. an entry in the timetable),
10782  and 'event' is reserved for and individual code line within a service. Repeats use the term 'item' if they use any at all.
10783 
10784  In references to 'HeadCode' can have an optional prefix - up to 4 additional characters that can be anything, so long as last 4 digits
10785  represent the headcode. This allows links to be uniquely identified regardless of the headcode - so can have same headcodes as often as
10786  user wishes
10787 
10788  Prior to start time, anything except a line beginning with a time [...leading spaces...] HH:MM is ignored - can be
10789  descriptive text or anything user wishes
10790  A time on its own line [HH:MM], with or without leading spaces, but with anything following it before the CR (which will
10791  be ignored) is taken as the timetable start time.
10792  Thereafter there must be text on every line in the timetable, as the first blank line (or end of file) will be taken as the end of the
10793  timetable. Text can follow the 'end of timetable' blank line if the user wishes.
10794  A line within the timetable beginning with '*', with or without leading spaces, is ignored. Such lines can add text
10795  within the timetable if required.
10796  Timetable entries consist of one line per headcode (i.e. per service, not necessarily per train, as one train can run several different
10797  services)
10798  Each line starts with HeadCode & full train information for a new train (Snt or Snt-sh), or, for a continuing service
10799  (Sfs, Sns, Sns-sh or Sns-fsh), can have (a) Headcode only or (b) HeadCode + Description, nothing else
10800 
10801  All leading & trailing spaces before & after a line or any entry in a line are stripped off - these can be included to make reading a
10802  text timetable file easier
10803 
10804  form:-
10805  HeadCode[;Description (plain text, no commas or semicolons)][;StartSpeed(kph); MaxRunningSpeed(kph); Mass(tonnes, prog converts to kg);
10806  MaxBrakeRate(tonnes force, prog converts to m/s/s); & gross power(kW, prog converts to power at rail in w)
10807  then multiple entries, separated by commas, of the form:-
10808 
10809  HH:MM;Snt;RearStartIdent FrontStartIdent }StartNew }
10810  HH:MM;Snt-sh;RearStartIdent FrontStartIdent;Fsh HeadCode }SNTShuttle }
10811  HH:MM;Sns-sh;Fxx-sh HeadCode;F-nshs HeadCode (non-repeating)}SNSShuttle }
10812 
10813  HH:MM;Command;HeadCode (Sfs Sns jbo fsp rsp Fns Fjo Frh-sh) }TimeCmdHeadCode } Train action entries
10814  HH:MM;F-nshs;NonRepeatingShuttleLinkHeadCode }FNSNonRepeatToShuttle }
10815  HH:MM;Sns-fsh;NonRepeatingShuttleLinkHeadCode }SNSNonRepeatFromShuttle }
10816 
10817  HH:MM;Command (cdt) }TimeCmd }
10818  HH:MM;Command;Description (dsc) }TimeCmdDescription }
10819  HH:MM;Location (arr & dep) }TimeLoc }
10820  HH:MM;HH:MM;Location }TimeTimeLoc }
10821  HH:MM;pas;Location }PassTime }
10822  HH:MM;Fns-sh;Snx-sh HeadCode;Sns-fsh HeadCode (non-rep) }FSHNewService }
10823  HH:MM;Fer;set of allowable IDs }ExitRailway }
10824  Command (Frh only) }FinRemHere }
10825 
10826  R;mm;dd;nn. Repeat Repeat entry
10827 
10828  Formats:
10829 
10830  Command only: Frh
10831  Time;Command: cdt
10832  Time;Command;Description dsc
10833  Time;Command;Headcode: Sfs Sns jbo fsp rsp Fns Fjo Frh-sh F-nshs Sns-fsh
10834  Time;Command;2 Element IDs: Snt
10835  Time;Comand;n Element IDs: Fer
10836  Time;Command;rep Headcode;nonrep Headcode: Sns-sh Fns-sh
10837  Time;Command;2 Element IDs;Headcode Snt-sh
10838  Time;Command;Location pas
10839  Time;Location Arr Dep
10840  Time;Time;Location Arr & dep together
10841 
10842  10 Non-linked entries: Snt (located or unlocated); pas; cdt; dsc; TimeLoc arr & dep; TimeTimeLoc; Fer; Frh
10843 
10844  9 1x Linked entries: Non-shuttle: fsp or rsp -> Sfs; Fns -> Sns; Fjo -> jbo; times must match, headcodes must match
10845  Shuttle: F-nshs -> Sns-sh: times match, F-nshs HeadCode matches Sns-sh 2nd Headcode;
10846  Fns-sh -> Sns-fsh: Fns-sh time + all repeats = Sns-fsh time, Fns-sh 2nd headcode matches Sns-fsh Headcode
10847 
10848  4 2x Linked entries, all shuttles:
10849 
10850  Frh-sh -> Snt-sh: Frh-sh time = Snt-sh time + 1 repeat while repeating, Frh-sh Headcode = Snt-sh Headcode;
10851  -> Sns-sh: Frh-sh time = Sns-sh time + 1 repeat while repeating, Frh-sh Headcode = Sns-sh 1st Headcode;
10852  -> Remain Here (at finish location after all repeats)
10853  Fns-sh -> Snt-sh: Frh-sh time = Snt-sh time + 1 repeat while repeating, Fns-sh 1st Headcode = Snt-sh Headcode
10854  -> Sns-sh: Frh-sh time = Sns-sh time + 1 repeat while repeating, Fns-sh 1st Headcode = Sns-sh 1st Headcode
10855 
10856  Moving/AtLoc states:-
10857 
10858  Successor state Type
10859 
10860  Snt located AtLoc ) Snt AtLoc successors: TimeLoc dep/jbo/fsp/rsp/cdt/dsc/Frh/Fns/Fjo/Frh-sh/Fns-sh/F-nshs;
10861  Snt Unlocated Moving ) Snt Moving successors: TimeLoc arr/TimeTimeLoc/pas/Fer;
10862  Sfs AtLoc )
10863  Sns AtLoc ) Start
10864  Sns-fsh AtLoc )
10865  Snt-sh AtLoc )
10866  Sns-sh AtLoc )
10867 
10868  pas Moving )
10869  jbo AtLoc )
10870  fsp AtLoc )
10871  rsp AtLoc ) Intermediate
10872  cdt AtLoc )
10873  dsc AtLoc )
10874  TimeLoc arr Moving (bef), AtLoc (aft) )
10875  TimeLoc dep AtLoc (bef), Moving (aft) )
10876  TimeTimeLoc Moving )
10877 
10878  Fns Repeat/Nothing)
10879  Fjo Repeat/Nothing)
10880  Frh Repeat/Nothing)
10881  Fer Repeat/Nothing) Finish
10882  Frh-sh Repeat )
10883  Fns-sh Repeat )
10884  F-nshs Nothing )
10885 
10886  Descriptions:
10887  Snt New train
10888  Sfs New service from split
10889  Sns New service from another service
10890  Sns-fsh New non-repeating service from a shuttle service
10891  Snt-sh New shuttle train at a timetabled stop
10892  Sns-sh New shuttle service from a feeder service
10893 
10894  pas Pass
10895  jbo Be joined by another train
10896  fsp Front split
10897  rsp Rear split
10898  cdt Change direction of train
10899  dsc Change description of train
10900  TimeLoc arr Arrival
10901  TimeLoc dep Departure
10902  TimeTimeLoc Arrival and departure
10903 
10904  Fns Finish & form a new service
10905  Fjo Finish & join another train
10906  Frh Finish & remain here
10907  Fer Finish & exit railway
10908  Frh-sh Finish & repeat shuttle, finally remain here
10909  Fns-sh Finish & repeat shuttle, finally form a non-repeating service
10910  F-nshs Finish & form a shuttle feeder service
10911 */
10912 
10913 bool TTrainController::TimetableIntegrityCheck(int Caller, char *FileName, bool GiveMessages, bool CheckLocationsExistInRailway) // true for success
10914 {
10915  // Error messages mainly given in called functions, five are given here - empty file; inability to find a start time; timetable containing
10916  // a line that is too long; timetable containing too few lines; and timetable failed to open.
10917  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TimetableIntegrityCheck," + AnsiString(FileName));
10918  // new for v0.2b
10919  // compile ActiveTrackElementNameMap
10920  TTrack::TActiveTrackElementNameMapEntry ActiveTrackElementNameMapEntry;
10921 
10923  for(unsigned int x = 0; x < Track->TrackVector.size(); x++)
10924  {
10925  // if((Track->TrackElementAt(, x).ActiveTrackElementName != "") && (Track->TrackElementAt(, x).TrackType != Continuation))
10927  == Track->ContinuationNameMap.end())
10928  {
10929  // exclude any name that appears in a continuation, error message given in tt validation if try to include such a name in a tt
10930  ActiveTrackElementNameMapEntry.first = Track->TrackElementAt(1035, x).ActiveTrackElementName;
10931  ActiveTrackElementNameMapEntry.second = 0; // this is a dummy value
10932  Track->ActiveTrackElementNameMap.insert(ActiveTrackElementNameMapEntry);
10933  }
10934  }
10936  // end of new section
10937  std::ifstream TTBLFile(FileName, std::ios_base::binary);
10938 
10939  // binary mode so the "\r\n" pairs stay as they are rather than being entered as '\n'
10940  if(TTBLFile.is_open())
10941  {
10942  char *TrainTimetableString = new char[10000];
10943  // enough for over 200 stations, should be adequate!
10944  bool EndOfFile = false;
10945  int Count = 0;
10946  // counts 'relevant' lines, i.e ignores any before the start time on its own line
10947  TTBLFile.getline(TrainTimetableString, 10000, '\0');
10948  // delimiter is '\0' as it's an AnsiString
10949  if(TTBLFile.eof() && (TrainTimetableString[0] == '\0'))
10950  // file empty - stores a null in 1st position if doesn't load any characters
10951  {
10952  // may still have eof even if read a line (no CRLF at end), and
10953  // if so need to process it
10954  TimetableMessage(GiveMessages, "Timetable invalid - file empty");
10955  TTBLFile.close();
10956  delete[] TrainTimetableString;
10957  Utilities->CallLogPop(1611);
10958  return(false);
10959  }
10960  AnsiString OneLine(TrainTimetableString);
10961  bool FinalCallFalse = false;
10962  while((Count == 0) && !ProcessOneTimetableLine(5, Count, OneLine, EndOfFile, FinalCallFalse, GiveMessages, CheckLocationsExistInRailway))
10963  // get rid of lines before the start time
10964  {
10965  // ProcessOneTimetableLine returns true for a valid start time, an EndOfFile &/or a blank entry
10966  TTBLFile.getline(TrainTimetableString, 10000, '\0');
10967  if(TTBLFile.eof() && (TrainTimetableString[0] == '\0'))
10968  // stores a null in 1st position if doesn't load any characters
10969  {
10970  // may still have eof even if read a line (no CRLF at end), and
10971  // if so need to process it
10972  TimetableMessage(GiveMessages, "Timetable invalid - unable to find a valid start time on its own line");
10973  TTBLFile.close();
10974  delete[] TrainTimetableString;
10975  Utilities->CallLogPop(772);
10976  return(false);
10977  }
10978  OneLine = AnsiString(TrainTimetableString);
10979  }
10980  // here when have accepted the start time
10981  Count++; // increment past the start time
10982  while(!EndOfFile)
10983  {
10984  TTBLFile.getline(TrainTimetableString, 10000, '\0');
10985  // get next line after start time
10986  if(TTBLFile.eof() && (TrainTimetableString[0] == '\0'))
10987  // stores a null in 1st position if doesn't load any characters
10988  {
10989  // may still have eof even if read a line (no CRLF at end), and
10990  // if so need to process it
10991  EndOfFile = true;
10992  OneLine = "";
10993  }
10994  else
10995  {
10996  OneLine = AnsiString(TrainTimetableString);
10997  }
10998  if(OneLine.Length() > 9999)
10999  {
11000  TimetableMessage(GiveMessages, "Timetable contains a line that is too long - 10,000 or more characters!");
11001  TTBLFile.close();
11002  delete[] TrainTimetableString;
11003  Utilities->CallLogPop(789);
11004  return(false);
11005  }
11006  bool FinalCallFalse = false;
11007  if(!ProcessOneTimetableLine(6, Count, OneLine, EndOfFile, FinalCallFalse, GiveMessages, CheckLocationsExistInRailway))
11008  // false for FinalCall - just checking at this stage
11009  {
11010  TTBLFile.close();
11011  delete[] TrainTimetableString;
11012  Utilities->CallLogPop(770);
11013  return(false);
11014  }
11015  if(EndOfFile && (Count < 2))
11016  // Timetable must contain at least two relevant lines, one for start time and at least one train
11017  {
11018  TimetableMessage(GiveMessages, "Timetable has too few or no relevant entries - must have a start time on its own line and at least one train");
11019  TTBLFile.close();
11020  delete[] TrainTimetableString;
11021  Utilities->CallLogPop(771);
11022  return(false);
11023  }
11024  Count++;
11025  }
11026  delete[] TrainTimetableString;
11027  TTBLFile.close();
11028  } // if(TTBLFile.is_open())
11029  else
11030  {
11031  TimetableMessage(GiveMessages, "Failed to open timetable file, make sure it's spelled correctly, it exists and isn't open in another application");
11032  Utilities->CallLogPop(2154);
11033  return(false);
11034  }
11035  Utilities->CallLogPop(753);
11036  return(true);
11037 }
11038 
11039 // ---------------------------------------------------------------------------
11040 
11041 bool TTrainController::ProcessOneTimetableLine(int Caller, int Count, AnsiString OneLine, bool &EndOfFile, bool FinalCall, bool GiveMessages,
11042  bool CheckLocationsExistInRailway) // return true for success
11043 
11044 /* Format:
11045  Prior to start time, anything except a line beginning with a time [...leading spaces...] HH:MM is ignored - can be
11046  descriptive text or anything user wishes
11047  A time on its own line [HH:MM], with or without leading spaces, but with anything following it before the CR (which will
11048  be ignored) is taken as the timetable start time.
11049  Thereafter there must be text on every line in the timetable, as the first blank line (or end of file) will be taken as the end of the
11050  timetable. Text can follow the 'end of timetable' blank line if the user wishes.
11051  A line within the timetable beginning with '*', with or without leading spaces, is ignored. Such lines can add text
11052  within the timetable if required.
11053  Timetable entries consist of one line per headcode (i.e. per service, not necessarily per train, as one train can run several different
11054  services)
11055  Each line starts with HeadCode & full train information for a new train (Snt or Snt-sh), or, for a continuing service
11056  (Sfs, Sns, Sns-sh or Sns-fsh), can have (a) Headcode only or (b) HeadCode + Description, nothing else
11057 
11058  All leading & trailing spaces before & after a line or any entry in a line are stripped off - these can be included to make reading a
11059  text timetable file easier
11060 
11061  form:-
11062  HeadCode[;Description (plain text, no commas or semicolons)][;StartSpeed(kph); MaxRunningSpeed(kph); Mass(tonnes, prog converts to kg);
11063  MaxBrakeRate(tonnes force, prog converts to m/s/s); & gross power(kW, prog converts to power at rail in w)
11064  then multiple entries, separated by commas, of the form:-
11065 
11066  Format FormatType
11067  [W]HH:MM;Command (cdt) }TimeCmd }
11068  [W]HH:MM;dsc;new description }TimeCmdDescription }
11069  [W]HH:MM;Fer;set of allowable IDs }ExitRailway }
11070  [W]HH:MM;pas;Location }PassTime }
11071  [W]HH:MM;Snt;RearStartIdent FrontStartIdent }StartNew }
11072  [W]HH:MM;Command;HeadCode (Sfs Sns jbo fsp rsp Fns Fjo Frh-sh) }TimeCmdHeadCode }
11073  [W]HH:MM;F-nshs;non-repeating headcode }FNSNonRepeatToShuttle }
11074  [W]HH:MM;Sns-fsh;NonRepeatingShuttleLinkHeadCode }SNSNonRepeatFromShuttle } Train action entries
11075  [W]HH:MM;Snt-sh;RearStartIdent FrontStartIdent;FSH HeadCode }SNTShuttle }
11076  [W]HH:MM;Sns-sh;FSH HeadCode;F-nshs HeadCode (non-repeating) }SNSShuttle }
11077  [W]HH:MM;Fns-sh;Details }FSHNewService }
11078  [W]HH:MM;Location (arr & dep) }TimeLoc }
11079  [W]HH:MM;HH:MM;Location }TimeTimeLoc }
11080  Command (Frh only) }FinRemHere }
11081 
11082  R;mm;dd;nn. Repeat Repeat entry
11083 
11084  Two times represent arrival & departure, without any other events between (if arrival and departure times are the same
11085  then departure is 30 sec after arrival), single time represents (a) event time; (b) arrival time if train not already
11086  at location; or (c) departure time if train already at location (including train started at location either as a new
11087  train or as a continuation service train at that location). All lines must contain a start entry and a finish entry,
11088  the finish being the last unless there is a repeat entry. The repeat entry begins with 'R', then the incremental
11089  minutes, incremental train headcode last 2 digits, and number of repeats.
11090 
11091  Shuttle entries are where can loop back to an earlier Snt-sh or Sns-sh entry from a Frh-sh or Fns-sh (Finish Shuttle)
11092  entry. Here the shuttle start can have two entries, one from a set position (Snt-sh, must be located) or from a F-nshs
11093  (Sns-sh) - with NO repeat from this source, and from a Fxx-sh, with repeats. After all shuttle repeats Frh-sh remains
11094  where it is, and Fns-sh links to a new service (via an Sns entry), but there must be no repeats in this new service
11095  (it's for a shuttle train to return to depot at end of services)
11096 
11097  Command/Location & details are as follows:-
11098 
11099  Although headcodes can be duplicated, all joins, splits, new services etc give other headcode from both trains' povs, and
11100  these have to match once only, i.e. if 2E44 splits to 2E45 then it can't split to 2E45 anywhere else, and 2E45 must give
11101  2E44 in its Sfs entry. All these are checked.
11102  ***add note re shuttles & their use of otherheadcodes + non-repeating headcodes***
11103 
11104  Start commands:-
11105  Snt (StartNew) = Start New Train, i.e. create new train, details = rearstartident, space, frontstartident (can't confuse
11106  with loc as a start entry can't have a location as details)
11107  Sfs (TimeCmdHeadCode) = Start From Split, create a new train that has split from another train (& listed in other train's
11108  timetable line), details = other headcode - (can't confuse with loc as start can't be a loc)
11109  Sns (TimeCmdHeadCode) = Start, headcode change from earlier service - no need to create train as already exists, it just
11110  changes its relevant information, details = old headcode (can't confuse with loc as start can't be a loc)
11111  Snt-sh (SNTShuttle) = Start New Train, i.e. create new train, details = rearstartident, space, frontstartident (can't
11112  confuse with loc as start can't be a loc) then the Fsh-XX service headcode (OtherHeadCode can't be same headcode)
11113  Sns-sh (SNSShuttle) = Start, headcode change from earlier service - no need to create train as already exists, it just
11114  changes its relevant information, details = the FSH-XX service headcode (OtherHeadCode, can't be same headcode)
11115  followed by the non-repeating F-nshs headcode (NonRepeatingShuttleLinkHeadCode)
11116  Sns-fsh (SNSNonRepeatFromShuttle) = Start as a non-repeating service from a shuttle service that has finished all its
11117  repeats, details = NonRepeatingShuttleLinkHeadCode for the corresponding shuttle Fns-sh service
11118 
11119  Intermediate commands:-
11120  Time - Location (TimeLoc), can be arrival or departure depending on context
11121  Time Time location (TimeTimeLoc), arrival and departure
11122  Location Name (exactly as used in the railway) in TimeLoc & TimeTimeLoc means that the train is required to stop at the location
11123  pas (PassTime), Time;pas;Location
11124  jbo (TimeCmdHeadCode) = Joined By Other = joined by other train, details = new headcode (await other train - may be delayed). Note that the
11125  joining train's finish details must correspond or the file check will fail
11126  fsp (TimeCmdHeadCode) = Front Split = a new train splits away from front of this train, both trains in same direction, details = new headcode (create
11127  new train - that train's starting information must correspond)
11128  rsp (TimeCmdHeadCode) = Rear Split = a new train splits away from rear of this train, both trains in same direction, details = new headcode (create
11129  new train - that train's starting information must correspond)
11130  cdt (TimeCmd) = Change Direction of Train = change direction, no details needed & no train creation
11131  dsc (TimeCmdDescription) = Change description of train = new description only, no train creation
11132 
11133  Finish commands:-
11134  Fns (TimeCmdHeadCode) = Finish New Service = finish, form new service in same direction, details = new headcode (no train
11135  creation)
11136  F-nshs (FNSShuttle) = Finish New Service (Shuttle) = finish, form new shuttle service in same direction, details =
11137  shuttle headcode (no train creation)
11138  Fjo (TimeCmdHeadCode) = Finish Join Other = finish, join other train (which must be on an adjacent element, either end -
11139  may have to wait for it), details = new headcode (delete train)
11140  Frh (FinRemHere) = Finish Remain Here = stay here indefinitely, no details & no time needed
11141  Fer (ExitRailway) = Finish, exit railway (i.e at a continuation) - details = set of allowable exit IDs
11142  Frh-sh (TimeCmdHeadCode) = Finish then restart as a shuttle using Snt-sh or Sns-sh, when all shuttle repeats done remain
11143  here
11144  Fns-sh (FSHNewService) = Finish then restart as a shuttle using Snt-sh or Sns-sh, when all shuttle repeats done form new
11145  service via Sns-fsh using the NonRepeatingShuttleLinkHeadCode
11146 
11147  Repeat:-
11148  R;mm;dd;nn (Repeat) where mm = minute increment, dd = 2nd 2 headcode digit increment & nn = no. of repeats (no max as can duplicate
11149  headcodes - it is up to user to avoid duplicates if he/she wishes to.
11150 
11151  Checks carried out with error messages in this function:-
11152  At least one comma in a service line (it's based on a .csv file)
11153  No entries following train information;
11154  At least one comma in remainder after train information (i.e at least a start and a finish entry);
11155  SplitEntry returns false in an intermediate entry - message repeats the entry for information;
11156  First entry not a start entry;
11157  Train information incomplete before a start entry;
11158  Entry follows a finish entry but doesn't begin with 'R';
11159  SplitEntry returns false in a finish entry - message repeats the entry for information;
11160  Last action entry isn't a finish entry.
11161 
11162  Function returns false with no message if:-
11163  Timetable start time invalid (no message is given for an invalid time as the line is assumed to be an irrelevant line; if no start
11164  time is found at all then an error message is given in the calling function);
11165  SplitTrainInfo returns false (message given in called function);
11166  SplitRepeat returns false (message given in called function).
11167 */
11168 {
11169  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ProcessOneTimetableLine," + AnsiString(Count) + "," + OneLine + "," +
11170  AnsiString((short)FinalCall) + "," + AnsiString((short)CheckLocationsExistInRailway));
11171  TTrainDataEntry TempTrainDataEntry;
11172 
11173  EndOfFile = false;
11174  StripSpaces(0, OneLine);
11175  // strip both leading and trailing spaces at ends of line and spaces before and after all commas and
11176  // semicolons within the line
11177  ServiceReference = "";
11178  if(OneLine != "")
11179  {
11180  if(OneLine[1] != '*')
11181  {
11182  int SCPos = OneLine.Pos(';');
11183  if(SCPos == 0)
11184  {
11185  ServiceReference = OneLine.SubString(1, 8);
11186  }
11187  else
11188  {
11189  ServiceReference = OneLine.SubString(1, (SCPos - 1));
11190  }
11191  }
11192  }
11193  bool AllCommas = true;
11194 
11195  for(int x = 1; x < OneLine.Length() + 1; x++) // check for nothing but commas (may be all commas if created from Excel) or a blank line
11196  {
11197  if(OneLine[x] != ',')
11198  {
11199  AllCommas = false;
11200  }
11201  }
11202  if(AllCommas || (OneLine == ""))
11203  {
11204  if(Count > 0)
11205  {
11206  EndOfFile = true;
11207  // returns true for a blank line (or a line of all commas) - treated as end of file
11208  Utilities->CallLogPop(1018);
11209  return(true);
11210  }
11211  else // count == 0 so not yet found a start time, no message to be given
11212  {
11213  Utilities->CallLogPop(754);
11214  return(false);
11215  }
11216  }
11217  AnsiString First = "", Second = "", Third = "", Fourth = "";
11218  int RearStartOrRepeatMins = 0, FrontStartOrRepeatDigits = 0, NumberOfRepeats = 0;
11219  TDateTime EventTime(0), ArrivalTime(0), DepartureTime(0);
11220  TDateTime StartTime(0);
11221  TNumList ExitList;
11222  bool Warning = false;
11223 
11224  if(Count == 0) // no start time found yet
11225  {
11226 /* dropped at v0.6b
11227  AnyHeadCodeValid = false;
11228  if(OneLine.SubString(6,5) == ";0000")
11229  {
11230  AnyHeadCodeValid = true;
11231  }
11232 */
11233  if(!CheckTimeValidity(0, OneLine, StartTime))
11234  {
11235  // no message is given for an invalid time as it's assumed to be an irrelevant line
11236  // if no start time is found at all then an error message is given in the calling function
11237  // AnyHeadCodeValid = false;
11238  Utilities->CallLogPop(755);
11239  return(false);
11240  }
11241  if(FinalCall) // here if start time valid
11242  {
11243  TTClockTime = StartTime;
11244  TimetableStartTime = StartTime;
11245  }
11246  }
11247  else
11248  {
11249  AnsiString TrainInfoStr = "", HeadCode = "", Description = "";
11250  int StartSpeed = 0, MaxRunningSpeed = 0, Mass = 0;
11251  double MaxBrakeRate = 0;
11252  double PowerAtRail = 0;
11253  int SignallerSpeed = 0;
11254  if(OneLine[1] == '*')
11255  {
11256  Utilities->CallLogPop(1581);
11257  return(true);
11258  // ignore any line beginning with '*' but return true as there is no error
11259  }
11260  int Pos = OneLine.Pos(',');
11261  if(Pos == 0)
11262  {
11263  int SubStringLength = 20;
11264  if(OneLine.Length() < 20)
11265  {
11266  SubStringLength = OneLine.Length();
11267  }
11268  TimetableMessage(GiveMessages, "Error in timetable - entry incomplete: see '" + OneLine.SubString(1, SubStringLength) + "'....");
11269  Utilities->CallLogPop(766);
11270  return(false);
11271  }
11272  TrainInfoStr = OneLine.SubString(1, Pos - 1);
11273  if(!SplitTrainInfo(0, TrainInfoStr, HeadCode, Description, StartSpeed, MaxRunningSpeed, Mass, MaxBrakeRate, PowerAtRail, SignallerSpeed,
11274  GiveMessages)) // error messages given in SplitTrainInfo
11275  {
11276  Utilities->CallLogPop(773);
11277  return(false);
11278  }
11279  if(FinalCall)
11280  {
11281  // store Train info - conversions done in SplitTrainInfo
11282  // only headcode mandatory for continuing services
11283  //HeadCode = ServiceReference until final section of SecondPassActions
11284  TempTrainDataEntry.HeadCode = HeadCode;
11285  TempTrainDataEntry.ServiceReference = HeadCode;
11286  TempTrainDataEntry.FixedDescription = Description; //name changed at v2.16.1
11287  TempTrainDataEntry.ExplicitDescription = false;
11288  if(Description != "")
11289  {
11290  TempTrainDataEntry.ExplicitDescription = true;
11291  }
11292  TempTrainDataEntry.StartSpeed = StartSpeed;
11293  TempTrainDataEntry.Mass = Mass;
11294  TempTrainDataEntry.MaxRunningSpeed = MaxRunningSpeed;
11295  TempTrainDataEntry.MaxBrakeRate = MaxBrakeRate;
11296  TempTrainDataEntry.PowerAtRail = PowerAtRail;
11297  TempTrainDataEntry.SignallerSpeed = SignallerSpeed;
11298  TTrainOperatingData TempTrainOperatingData;
11299  TempTrainDataEntry.TrainOperatingDataVector.push_back(TempTrainOperatingData); // push empty vector for now
11300  }
11301  AnsiString NewRemainder = OneLine.SubString(Pos + 1, OneLine.Length() - Pos);
11302  // now left with series of entries for this train, but there may be a string of commas at the end of the line if created by Excel
11303  // so strip them off
11304  while(NewRemainder[NewRemainder.Length()] == ',')
11305  {
11306  if(NewRemainder.Length() > 1)
11307  {
11308  NewRemainder = NewRemainder.SubString(1, NewRemainder.Length() - 1);
11309  }
11310  else
11311  {
11312  NewRemainder = "";
11313  break;
11314  }
11315  }
11316  // check if zero length & fail if so
11317  if(NewRemainder == "")
11318  {
11319  TimetableMessage(GiveMessages, "Error in timetable - no events following train: '" + OneLine + "'");
11320  Utilities->CallLogPop(769);
11321  return(false);
11322  }
11323  // now have one more entry than there are commas
11324  int CommaCount = 0;
11325  for(int x = 1; x < NewRemainder.Length() + 1; x++)
11326  {
11327  if(NewRemainder[x] == ',')
11328  {
11329  CommaCount++;
11330  }
11331  } // must have at least 1 comma, for start & finish entries, unless train is entered under signaller control
11332  if(CommaCount == 0)
11333  {
11334  if((NewRemainder.SubString(7, 3) != "Snt") || (NewRemainder[NewRemainder.Length()] != 'S'))
11335  {
11336  int SubStringLength = 20;
11337  if(OneLine.Length() < 20)
11338  {
11339  SubStringLength = OneLine.Length();
11340  }
11341  TimetableMessage(GiveMessages,
11342  "Error in timetable - must have at least a start and a finish event for a train that is not started under signaller control - see line beginning: '" +
11343  OneLine.SubString(1, SubStringLength) + "'....");
11344  Utilities->CallLogPop(783);
11345  return(false);
11346  }
11347  }
11348  AnsiString OneEntry = "";
11349  TTimetableFormatType FormatType;
11350  TTimetableSequenceType SequenceType;
11351  TTimetableLocationType LocationType;
11352  TTimetableShuttleLinkType ShuttleLinkType;
11353  bool FinishFlag = false;
11354 // bool NewTrain = false;//added at v2.14.0 to record created trains for later zero power checks <-- not needed after zero power restriction dropped
11355  for(int x = 0; x < CommaCount + 1; x++)
11356  {
11357  if((CommaCount == 0) || (x < CommaCount))
11358  // i.e. train entered under signaller control with no repeats, or entry is not the last,
11359  // in which case there's a comma & finish element or repeat still to come this entry could
11360  // be a finish but can't be a repeat
11361  {
11362  if(CommaCount == 0)
11363  {
11364  OneEntry = NewRemainder;
11365  NewRemainder = "";
11366  }
11367  else
11368  {
11369  Pos = NewRemainder.Pos(',');
11370  OneEntry = NewRemainder.SubString(1, Pos - 1);
11371  NewRemainder = NewRemainder.SubString(Pos + 1, NewRemainder.Length() - Pos);
11372  }
11373  First = "";
11374  Second = "";
11375  Third = "";
11376  Fourth = "";
11377  RearStartOrRepeatMins = 0;
11378  FrontStartOrRepeatDigits = 0;
11379  NumberOfRepeats = 0;
11380  if(!SplitEntry(0, OneEntry, GiveMessages, CheckLocationsExistInRailway, First, Second, Third, Fourth, RearStartOrRepeatMins,
11381  FrontStartOrRepeatDigits, FormatType, LocationType, SequenceType, ShuttleLinkType, ExitList, Warning))
11382  {
11383  TimetableMessage(GiveMessages, "Error in timetable - Event: '" + OneEntry + "'");
11384  Utilities->CallLogPop(756);
11385  return(false);
11386  }
11387 /* not needed at v2.19.0
11388  if((Second == "Snt") || (Second == "Snt-sh")) //added at v2.14.0, see above
11389  {
11390  NewTrain = true; not needed when zero power restrictions removed
11391  }
11392 */
11393  // check if warning for Frh or Fjo & reject
11394  if(Warning && (Second == "Frh"))
11395  {
11396  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry + "': warnings cannot be given for 'Frh' events");
11397  Utilities->CallLogPop(1793);
11398  return(false);
11399  }
11400  if(Warning && (Second == "Fjo"))
11401  {
11402  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
11403  "': warnings cannot be given for 'Fjo' events, for a train join warning add a 'W' prefix to the 'jbo' event");
11404  Utilities->CallLogPop(1794);
11405  return(false);
11406  }
11407  //below added at v2.14.0 to prevent unpowered trains attempting to be joined by (Second == jbo), split (Second -- fsp or rsp),
11408  //or change direction. Form a new service dealt with below for zero power as it's a finish event. <-- removed at v2.19.0
11409 
11410 /* restriction removed at v2.19.0
11411  if(NewTrain && (PowerAtRail < 1) && (Second == "jbo"))
11412  {
11413  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
11414  "': a train created without power can't 'be joined by' another train (i.e. can't include command 'jbo'), "
11415  "use command 'Fjo' (i.e. 'join' another train) instead immediately after the line containing 'Snt', and use "
11416  "command 'jbo' for the train it is to join.");
11417  Utilities->CallLogPop(2545);
11418  return(false);
11419  }
11420  if(NewTrain && (PowerAtRail < 1) && ((Second == "fsp") || (Second == "rsp")))
11421  {
11422  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
11423  "': a train created without power can't split.");
11424  Utilities->CallLogPop(2546);
11425  return(false);
11426  }
11427  if(NewTrain && (PowerAtRail < 1) && (Second == "cdt"))
11428  {
11429  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
11430  "': a train created without power can't change direction under timetable control.");
11431  Utilities->CallLogPop(2547);
11432  return(false);
11433  }
11434 */
11435  //end of new additions
11436  if(x == 0) // should be start event
11437  {
11438  if(SequenceType != StartSequence)
11439  {
11440  TimetableMessage(GiveMessages, "Error in timetable - First event not a start event: '" + OneEntry + "'");
11441  Utilities->CallLogPop(784);
11442  return(false);
11443  }
11444  if((Second == "Snt") && (Fourth == 'S') && (NewRemainder != ""))
11445  {
11446  if(NewRemainder[1] != 'R')
11447  {
11448  TimetableMessage(GiveMessages,
11449  "Error in timetable - the only event that can follow a train created under signaller control is a repeat, see '" +
11450  OneEntry + "'");
11451  Utilities->CallLogPop(787);
11452  return(false);
11453  }
11454  }
11455  if((Second == "Snt") || (Second == "Snt-sh"))
11456  // need full train information including non-default values for at least HeadCode, Description,
11457  // MaxRunningSpeed, Mass, MaxBrakeRate, & PowerAtRail
11458  {
11459  if((HeadCode == "") || (Description == "") || (MaxRunningSpeed == 0) || (Mass == 0) || (MaxBrakeRate == 0)) // ||
11460  // (PowerAtRail == 0)) allowed 0 for power at v2.4.0
11461  {
11462  TimetableMessage(GiveMessages, "Error in timetable - train information incomplete before 'Snt' or 'Snt-sh' start event: '" +
11463  OneEntry + "'");
11464  Utilities->CallLogPop(1783);
11465  return(false);
11466  }
11467  }
11468  if((Second == "Sfs") || (Second == "Sns") || (Second == "Sns-sh") || (Second == "Sns-fsh"))
11469  // service continuation - need at least non-default value for HeadCode
11470  {
11471  if(HeadCode == "")
11472  {
11473  TimetableMessage(GiveMessages, "Error in timetable - headcode missing before 'Sfs', 'Sns', 'Sns-sh' or 'Sns-fsh' start event: '" +
11474  OneEntry + "'");
11475  Utilities->CallLogPop(788);
11476  return(false);
11477  }
11478  if((StartSpeed != 0) || (MaxRunningSpeed != 0) || (Mass != 0) || (MaxBrakeRate != 0) || (PowerAtRail != 0))
11479  {
11480  TimetableMessage(GiveMessages,
11481  "Error in timetable - information additional to a headcode & optional description given before 'Sfs', 'Sns', 'Sns-sh' or 'Sns-fsh' start event: '" +
11482  OneEntry + "'");
11483  Utilities->CallLogPop(843);
11484  return(false);
11485  }
11486  }
11487  }
11488  if(SequenceType == FinishSequence)
11489  {
11490  FinishFlag = true;
11491  // marker for only permitted additional entry being a repeat, only needed if the
11492  // finish entry is not the last entry
11493  }
11494  if(FinalCall)
11495  {
11496  // interpret & add to ActionVector
11497  TDateTime TempTime;
11498  TActionVectorEntry ActionVectorEntry;
11499  ActionVectorEntry.FormatType = FormatType;
11500  ActionVectorEntry.LocationType = LocationType;
11501  ActionVectorEntry.SequenceType = SequenceType;
11502  ActionVectorEntry.ShuttleLinkType = ShuttleLinkType;
11503  ActionVectorEntry.Warning = Warning;
11504  if(FormatType == TimeLoc)
11505  {
11506  if(CheckTimeValidity(1, First, ActionVectorEntry.EventTime))
11507  {
11508  ;
11509  } // these will all be true as final call
11510 
11511  ActionVectorEntry.LocationName = Second;
11512  }
11513  else if(FormatType == PassTime) // new
11514  {
11515  if(CheckTimeValidity(17, First, ActionVectorEntry.EventTime))
11516  {
11517  ;
11518  }
11519  ActionVectorEntry.Command = Second;
11520  ActionVectorEntry.LocationName = Third;
11521  }
11522  else if(FormatType == TimeTimeLoc)
11523  {
11524  if(CheckTimeValidity(2, First, ActionVectorEntry.ArrivalTime))
11525  {
11526  ;
11527  }
11528  if(CheckTimeValidity(3, Second, ActionVectorEntry.DepartureTime))
11529  {
11530  ;
11531  }
11532  ActionVectorEntry.LocationName = Third;
11533  }
11534  else if(FormatType == TimeCmd)
11535  {
11536  if(CheckTimeValidity(4, First, ActionVectorEntry.EventTime))
11537  {
11538  ;
11539  }
11540  ActionVectorEntry.Command = Second;
11541  }
11542  else if(FormatType == ExitRailway)
11543  {
11544  if(CheckTimeValidity(18, First, ActionVectorEntry.EventTime))
11545  {
11546  ;
11547  }
11548  ActionVectorEntry.Command = Second;
11549  ActionVectorEntry.ExitList = ExitList;
11550  }
11551  else if(FormatType == StartNew)
11552  {
11553  if(CheckTimeValidity(5, First, ActionVectorEntry.EventTime))
11554  {
11555  ;
11556  }
11557  ActionVectorEntry.Command = Second;
11558  ActionVectorEntry.RearStartOrRepeatMins = RearStartOrRepeatMins;
11559  ActionVectorEntry.FrontStartOrRepeatDigits = FrontStartOrRepeatDigits;
11560  if(Fourth == 'S')
11561  {
11562  ActionVectorEntry.SignallerControl = true;
11563  }
11564  }
11565  else if(FormatType == SNTShuttle)
11566  {
11567  if(CheckTimeValidity(6, First, ActionVectorEntry.EventTime))
11568  {
11569  ;
11570  }
11571  ActionVectorEntry.Command = Second;
11572  ActionVectorEntry.RearStartOrRepeatMins = RearStartOrRepeatMins;
11573  ActionVectorEntry.FrontStartOrRepeatDigits = FrontStartOrRepeatDigits;
11574  ActionVectorEntry.OtherHeadCode = Fourth;
11575  }
11576  else if(FormatType == SNSShuttle)
11577  {
11578  if(CheckTimeValidity(7, First, ActionVectorEntry.EventTime))
11579  {
11580  ;
11581  }
11582  ActionVectorEntry.Command = Second;
11583  ActionVectorEntry.OtherHeadCode = Third;
11584  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Fourth;
11585  }
11586  else if(FormatType == TimeCmdHeadCode) //fsp/rsp is TimeCmdHeadCode but may have a Fourth - see below
11587  {
11588  if(CheckTimeValidity(8, First, ActionVectorEntry.EventTime))
11589  {
11590  ;
11591  }
11592  ActionVectorEntry.Command = Second;
11593  ActionVectorEntry.OtherHeadCode = Third;
11594  if((Second == "fsp") || (Second == "rsp")) //new at v2.15.0 & checked in SplitEntry
11595  {
11596  ActionVectorEntry.SplitDistribution = "50-50"; //added at v2.18.0
11597  if(Fourth != "")
11598  {
11599  ActionVectorEntry.SplitDistribution = Fourth;
11600  }
11601  }
11602  }
11603  else if((FormatType == FNSNonRepeatToShuttle) || (FormatType == SNSNonRepeatFromShuttle))
11604  {
11605  if(CheckTimeValidity(9, First, ActionVectorEntry.EventTime))
11606  {
11607  ;
11608  }
11609  ActionVectorEntry.Command = Second;
11610  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Third;
11611  }
11612  else if(FormatType == FSHNewService)
11613  {
11614  if(CheckTimeValidity(10, First, ActionVectorEntry.EventTime))
11615  {
11616  ;
11617  }
11618  ActionVectorEntry.Command = Second;
11619  ActionVectorEntry.OtherHeadCode = Third;
11620  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Fourth;
11621  }
11622  else if(FormatType == FinRemHere)
11623  {
11624  ActionVectorEntry.Command = Second;
11625  }
11626  else if(FormatType == TimeCmdDescription) //new at v2.15.0
11627  {
11628  if(CheckTimeValidity(35, First, ActionVectorEntry.EventTime))
11629  {
11630  ;
11631  }
11632  ActionVectorEntry.Command = Second;
11633  ActionVectorEntry.NewDescription = Third;
11634  }
11635  TempTrainDataEntry.ActionVector.push_back(ActionVectorEntry);
11636  }
11637  }
11638  else // last entry, & not entered under signaller control with no repeats, i.e. could be finish or repeat
11639  {
11640  OneEntry = NewRemainder;
11641  First = "";
11642  Second = "";
11643  Third = "";
11644  Fourth = "";
11645  RearStartOrRepeatMins = 0;
11646  FrontStartOrRepeatDigits = 0;
11647  NumberOfRepeats = 0;
11648  if((FinishFlag) && (OneEntry[1] != 'R'))
11649  // already had a finish entry
11650  {
11651  TimetableMessage(GiveMessages, "Error in timetable - Last event = '" + OneEntry + "'. An earlier finish event has been found with something other than a repeat following it - only a repeat can follow a finish event.");
11652  Utilities->CallLogPop(79);
11653  return(false);
11654  }
11655  if(OneEntry[1] != 'R') // must be finish
11656  {
11657  if(!SplitEntry(1, OneEntry, GiveMessages, CheckLocationsExistInRailway, First, Second, Third, Fourth, RearStartOrRepeatMins,
11658  FrontStartOrRepeatDigits, FormatType, LocationType, SequenceType, ShuttleLinkType, ExitList, Warning))
11659  {
11660  TimetableMessage(GiveMessages, "Error in timetable - Event: '" + OneEntry + "'.\nIf the program version is not the latest the "
11661  "timetable may have features that aren't compatible with the version in use.");
11662  Utilities->CallLogPop(757);
11663  return(false);
11664  }
11665  //below added at v2.14.0 to prevent unpowered trains attempting to form a new service.
11666 /* restriction removed at v2.19.0
11667  if(NewTrain && (PowerAtRail < 1) && ((Second == "Fns") || (Second == "Frh-sh") || (Second == "Fns-sh") || (Second == "F-nshs")))
11668  {
11669  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
11670  "': a train created without power can't form a new service.");
11671  Utilities->CallLogPop(2548);
11672  return(false);
11673  }
11674  //end of new additions
11675 */
11676  if(SequenceType != FinishSequence)
11677  {
11678  TimetableMessage(GiveMessages, "Error in timetable - last event should be a finish: '" + OneEntry + "'");
11679  Utilities->CallLogPop(785);
11680  return(false);
11681  }
11682  if(FinalCall)
11683  {
11684  // interpret & add to ActionVector
11685  TDateTime TempTime;
11686  TActionVectorEntry ActionVectorEntry;
11687  ActionVectorEntry.FormatType = FormatType;
11688  ActionVectorEntry.LocationType = LocationType;
11689  ActionVectorEntry.SequenceType = SequenceType;
11690  ActionVectorEntry.ShuttleLinkType = ShuttleLinkType;
11691  ActionVectorEntry.Warning = Warning;
11692  if(FormatType == TimeCmd)
11693  {
11694  if(CheckTimeValidity(11, First, ActionVectorEntry.EventTime))
11695  {
11696  ;
11697  }
11698  ActionVectorEntry.Command = Second;
11699  }
11700  else if(FormatType == TimeCmdHeadCode)
11701  {
11702  if(CheckTimeValidity(12, First, ActionVectorEntry.EventTime))
11703  {
11704  ;
11705  }
11706  ActionVectorEntry.Command = Second;
11707  ActionVectorEntry.OtherHeadCode = Third;
11708  }
11709  else if(FormatType == FNSNonRepeatToShuttle)
11710  {
11711  if(CheckTimeValidity(13, First, ActionVectorEntry.EventTime))
11712  {
11713  ;
11714  }
11715  ActionVectorEntry.Command = Second;
11716  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Third;
11717  }
11718  else if(FormatType == FSHNewService)
11719  {
11720  if(CheckTimeValidity(14, First, ActionVectorEntry.EventTime))
11721  {
11722  ;
11723  }
11724  ActionVectorEntry.Command = Second;
11725  ActionVectorEntry.OtherHeadCode = Third;
11726  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Fourth;
11727  }
11728  else if(FormatType == ExitRailway)
11729  {
11730  if(CheckTimeValidity(19, First, ActionVectorEntry.EventTime))
11731  {
11732  ;
11733  }
11734  ActionVectorEntry.Command = Second;
11735  ActionVectorEntry.ExitList = ExitList;
11736  }
11737  else if(FormatType == FinRemHere)
11738  {
11739  ActionVectorEntry.Command = Second;
11740  }
11741  TempTrainDataEntry.ActionVector.push_back(ActionVectorEntry);
11742  }
11743  }
11744  else // repeat
11745  {
11746  if(!SplitRepeat(0, OneEntry, RearStartOrRepeatMins, FrontStartOrRepeatDigits, NumberOfRepeats, GiveMessages))
11747  {
11748  Utilities->CallLogPop(786);
11749  // error messages given in SplitRepeat
11750  return(false);
11751  }
11752  if(FinalCall)
11753  {
11754  TActionVectorEntry ActionVectorEntry;
11755  ActionVectorEntry.FormatType = Repeat;
11756  ActionVectorEntry.LocationType = LocTypeForRepeatEntry;
11757  ActionVectorEntry.SequenceType = SequTypeForRepeatEntry;
11758  ActionVectorEntry.ShuttleLinkType = ShuttleLinkTypeForRepeatEntry;
11759  ActionVectorEntry.RearStartOrRepeatMins = RearStartOrRepeatMins;
11760  ActionVectorEntry.FrontStartOrRepeatDigits = FrontStartOrRepeatDigits;
11761  ActionVectorEntry.NumberOfRepeats = NumberOfRepeats;
11762  TempTrainDataEntry.ActionVector.push_back(ActionVectorEntry);
11763  }
11764  }
11765  }
11766  }
11767  if(FinalCall)
11768  {
11769  TrainDataVector.push_back(TempTrainDataEntry);
11770  }
11771  }
11772  Utilities->CallLogPop(80);
11773  return(true);
11774 }
11775 
11776 // ---------------------------------------------------------------------------
11777 
11778 bool TTrainController::Last2CharactersBothDigits(int Caller, AnsiString HeadCode)
11779 {
11780  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",Last2CharactersBothDigits," + HeadCode);
11781  if((HeadCode[HeadCode.Length() - 1] < '0') || (HeadCode[HeadCode.Length() - 1] > '9'))
11782  {
11783  Utilities->CallLogPop(1890);
11784  return(false);
11785  }
11786  if((HeadCode[HeadCode.Length()] < '0') || (HeadCode[HeadCode.Length()] > '9'))
11787  {
11788  Utilities->CallLogPop(1891);
11789  return(false);
11790  }
11791  Utilities->CallLogPop(1892);
11792  return(true);
11793 }
11794 
11795 // ---------------------------------------------------------------------------
11796 
11797 bool TTrainController::CheckTimeValidity(int Caller, AnsiString TimeStr, TDateTime &Time)
11798 // 1st 5 chars must be HH:MM, anything else will be ignored
11799 {
11800  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckTimeValidity," + TimeStr);
11801  if(TimeStr.Length() < 5)
11802  {
11803  Utilities->CallLogPop(926);
11804  return(false);
11805  }
11806  if((TimeStr[1] < '0') || (TimeStr[1] > '9'))
11807  {
11808  Utilities->CallLogPop(927);
11809  return(false);
11810  }
11811  if((TimeStr[2] < '0') || (TimeStr[2] > '9'))
11812  {
11813  Utilities->CallLogPop(928);
11814  return(false);
11815  }
11816  if(TimeStr[3] != ':')
11817  {
11818  Utilities->CallLogPop(929);
11819  return(false);
11820  }
11821  if((TimeStr[4] < '0') || (TimeStr[4] > '5'))
11822  {
11823  Utilities->CallLogPop(930);
11824  return(false);
11825  }
11826  if((TimeStr[5] < '0') || (TimeStr[5] > '9'))
11827  {
11828  Utilities->CallLogPop(931);
11829  return(false);
11830  }
11831  while(TimeStr.Length() > 5)
11832  {
11833  TimeStr = TimeStr.SubString(1, TimeStr.Length() - 1);
11834  }
11835  double WholeHours = (AnsiString(TimeStr[1]) + AnsiString(TimeStr[2])).ToDouble();
11836  double FracHour = ((AnsiString(TimeStr[4]) + AnsiString(TimeStr[5])).ToDouble()) / 60.0;
11837 
11838  if((WholeHours + FracHour) >= 95.98334)
11839  {
11840  Utilities->CallLogPop(1817);
11841  return(false); // > 95h 59m
11842  }
11843  Time = TDateTime((WholeHours + FracHour) / 24);
11844  Utilities->CallLogPop(932);
11845  return(true);
11846 }
11847 
11848 // ---------------------------------------------------------------------------
11849 
11850 bool TTrainController::SplitEntry(int Caller, AnsiString OneEntry, bool GiveMessages, bool CheckLocationsExistInRailway, AnsiString &First, AnsiString &Second,
11851  AnsiString &Third, AnsiString &Fourth, int &RearStartOrRepeatMins, int &FrontStartOrRepeatDigits, TTimetableFormatType &FormatType,
11852  TTimetableLocationType &LocationType, TTimetableSequenceType &SequenceType, TTimetableShuttleLinkType &ShuttleLinkType, TNumList &ExitList, bool &Warning)
11853 /* This is a train action entry from a single line of the timetable, i.e. not train information and not a repeat entry.
11854  Return false for failure.
11855  See description above under ProcessOneTimetableLinefor details of train action entries
11856  NB all types set except LocationType for Snt as may be located or not
11857 */{
11858  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SplitEntry," + OneEntry);
11859  Warning = false;
11860  TDateTime TempTime;
11861 
11862  if(OneEntry.Length() > 0)
11863  {
11864  if(OneEntry[1] == 'W') // warning
11865  {
11866  Warning = true;
11867  OneEntry = OneEntry.SubString(2, OneEntry.Length() - 1);
11868  // strip it off
11869  }
11870  }
11871  if(OneEntry == "Frh")
11872  {
11873  FormatType = FinRemHere;
11874  SequenceType = FinishSequence;
11875  LocationType = AtLocation;
11876  ShuttleLinkType = NotAShuttleLink;
11877  Second = "Frh";
11878  Utilities->CallLogPop(1016);
11879  return(true);
11880  }
11881  if(OneEntry.Length() < 7)
11882  {
11883  Utilities->CallLogPop(907);
11884  return(false); // 'HH:MM;' + at least a one-letter location name
11885  }
11886  int Pos = OneEntry.Pos(';'); // first segment delimiter
11887 
11888  if(Pos != 6)
11889  {
11890  Utilities->CallLogPop(908);
11891  return(false);
11892  // no delimiter or delimiter not in position 6, has to be a time so fail
11893  }
11894  First = OneEntry.SubString(1, 5); // has to be a time
11895  if(!CheckTimeValidity(16, First, TempTime))
11896  {
11897  Utilities->CallLogPop(909);
11898  return(false);
11899  }
11900  AnsiString Remainder = OneEntry.SubString(Pos + 1, OneEntry.Length() - Pos);
11901 
11902 // if((Remainder[1] >= '0') && (Remainder[1] <= '9')) changed at v2.16.0 so only 'digit-digit-colon....' interpreted as a time - to allow locations to begin with digits
11903  if((Remainder.Length() >= 3) && (Remainder[1] >= '0') && (Remainder[1] <= '9') && (Remainder[2] >= '0') && (Remainder[2] <= '9') && (Remainder[3] == ':'))
11904  // next segment is a time so this is a TimeTimeLoc & 3rd seg has to be a location to be valid
11905  {
11906  if(Remainder.Length() < 7)
11907  {
11908  Utilities->CallLogPop(910);
11909  return(false); // 'HH:MM;' + at least a one-letter location name
11910  }
11911  Pos = Remainder.Pos(';'); // second segment delimiter
11912  if(Pos == 0)
11913  {
11914  Utilities->CallLogPop(911);
11915  return(false);
11916  // no delimiter, has to be one between departure time & location
11917  }
11918  Second = Remainder.SubString(1, 5); // has to be a time
11919  if(!CheckTimeValidity(15, Second, TempTime))
11920  {
11921  Utilities->CallLogPop(912);
11922  return(false);
11923  }
11924  Third = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
11925  if(!CheckLocationValidity(0, Third, GiveMessages, CheckLocationsExistInRailway))
11926  {
11927  Utilities->CallLogPop(913);
11928  return(false);
11929  }
11930  FormatType = TimeTimeLoc;
11931  SequenceType = IntermediateSequence;
11932  LocationType = AtLocation;
11933  ShuttleLinkType = NotAShuttleLink;
11934  Utilities->CallLogPop(914);
11935  return(true);
11936  }
11937  Pos = Remainder.Pos(';'); // second segment delimiter
11938  if(Pos == 0) // no third segment so second must be a location, or cdt
11939  {
11940  Second = Remainder;
11941  if(Second == "cdt")
11942  {
11943  FormatType = TimeCmd;
11944  ShuttleLinkType = NotAShuttleLink;
11945  LocationType = AtLocation;
11946  SequenceType = IntermediateSequence;
11947  Utilities->CallLogPop(915);
11948  return(true);
11949  }
11950  if(!CheckLocationValidity(1, Second, GiveMessages, CheckLocationsExistInRailway))
11951  {
11952  Utilities->CallLogPop(916);
11953  return(false);
11954  }
11955  else
11956  {
11957  FormatType = TimeLoc;
11958  LocationType = AtLocation;
11959  SequenceType = IntermediateSequence;
11960  ShuttleLinkType = NotAShuttleLink;
11961  Utilities->CallLogPop(917);
11962  return(true);
11963  }
11964  }
11965  // here if second segment is a command, with a third & maybe fourth segments
11966  if((Pos != 4) && (Pos != 7) && (Pos != 8))
11967  {
11968  Utilities->CallLogPop(918);
11969  return(false);
11970  // no third segement or not in position 4 or 7, & should be since all commands are 3, 6 or 7 letters
11971  }
11972  Second = Remainder.SubString(1, Pos - 1); // command
11973 
11974  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
11975  // details
11976  Pos = Remainder.Pos(';'); // third segment delimiter
11977  if(Pos == 0)
11978  {
11979  Third = Remainder;
11980  }
11981  else
11982  {
11983  Third = Remainder.SubString(1, Pos - 1);
11984  Fourth = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
11985  }
11986 
11987  if((Second == "Snt") || (Second == "Snt-sh"))
11988  // third has to be 2 element idents with a space between
11989  {
11990  int SpacePos = Third.Pos(' ');
11991  if(SpacePos == 0)
11992  {
11993  Utilities->CallLogPop(919);
11994  return(false); // no space
11995  }
11996  AnsiString RearStartStr = Third.SubString(1, SpacePos - 1);
11997  AnsiString FrontStartStr = Third.SubString(SpacePos + 1, Third.Length() - SpacePos);
11998  // int RearPosition=0, FrontPosition=0, RearExitPos=0;
11999  if(CheckLocationsExistInRailway)
12000  {
12001  if(!CheckStartPositionValidity(0, RearStartStr, FrontStartStr, GiveMessages))
12002  {
12003  Utilities->CallLogPop(920);
12004  return(false);
12005  }
12006  RearStartOrRepeatMins = Track->GetTrackVectorPositionFromString(3, RearStartStr, GiveMessages);
12007  FrontStartOrRepeatDigits = Track->GetTrackVectorPositionFromString(4, FrontStartStr, GiveMessages);
12008  }
12009  if(Second == "Snt")
12010  {
12011  FormatType = StartNew;
12012  SequenceType = StartSequence;
12013  LocationType = NoLocation;
12014  // can't be set until know whether located or not - done in SecondPassActions
12015  ShuttleLinkType = NotAShuttleLink;
12016  }
12017  else // Snt-sh
12018  {
12019  FormatType = SNTShuttle;
12020  LocationType = AtLocation;
12021  SequenceType = StartSequence;
12022  ShuttleLinkType = ShuttleLink;
12023  if(!CheckHeadCodeValidity(0, GiveMessages, Fourth))
12024  {
12025  Utilities->CallLogPop(1038);
12026  return(false);
12027  }
12028  }
12029  Utilities->CallLogPop(921);
12030  return(true);
12031  }
12032  if(Second == "Sns-sh") // third & fourth have to be headcodes
12033  {
12034  FormatType = SNSShuttle;
12035  LocationType = AtLocation;
12036  SequenceType = StartSequence;
12037  ShuttleLinkType = ShuttleLink;
12038  if(!CheckHeadCodeValidity(1, GiveMessages, Third))
12039  {
12040  Utilities->CallLogPop(1039);
12041  return(false);
12042  }
12043  if(!CheckHeadCodeValidity(2, GiveMessages, Fourth))
12044  {
12045  Utilities->CallLogPop(1040);
12046  return(false);
12047  }
12048  Utilities->CallLogPop(1041);
12049  return(true);
12050  }
12051  if(Second == "F-nshs")
12052  {
12053  FormatType = FNSNonRepeatToShuttle;
12054  LocationType = AtLocation;
12055  SequenceType = FinishSequence;
12056  ShuttleLinkType = ShuttleLink;
12057  if(!CheckHeadCodeValidity(3, GiveMessages, Third))
12058  {
12059  Utilities->CallLogPop(1047);
12060  return(false);
12061  }
12062  Utilities->CallLogPop(1048);
12063  return(true);
12064  }
12065  if(Second == "Sns-fsh")
12066  {
12067  FormatType = SNSNonRepeatFromShuttle;
12068  LocationType = AtLocation;
12069  SequenceType = StartSequence;
12070  ShuttleLinkType = ShuttleLink;
12071  if(!CheckHeadCodeValidity(4, GiveMessages, Third))
12072  {
12073  Utilities->CallLogPop(1098);
12074  return(false);
12075  }
12076  Utilities->CallLogPop(1099);
12077  return(true);
12078  }
12079  if(Second == "Fns-sh") // third & fourth have to be headcodes
12080  {
12081  FormatType = FSHNewService;
12082  LocationType = AtLocation;
12083  SequenceType = FinishSequence;
12084  ShuttleLinkType = ShuttleLink;
12085  if(!CheckHeadCodeValidity(5, GiveMessages, Third))
12086  {
12087  Utilities->CallLogPop(1050);
12088  return(false);
12089  }
12090  if(!CheckHeadCodeValidity(6, GiveMessages, Fourth))
12091  {
12092  Utilities->CallLogPop(1051);
12093  return(false);
12094  }
12095  Utilities->CallLogPop(1052);
12096  return(true);
12097  }
12098  // new segment for 'pas'
12099  if(Second == "pas") // third has to be a location
12100  {
12101  FormatType = PassTime;
12102  LocationType = EnRoute;
12103  SequenceType = IntermediateSequence;
12104  ShuttleLinkType = NotAShuttleLink;
12105  if(!CheckLocationValidity(2, Third, GiveMessages, CheckLocationsExistInRailway))
12106  {
12107  Utilities->CallLogPop(1515);
12108  return(false);
12109  }
12110  Utilities->CallLogPop(1516);
12111  return(true);
12112  }
12113  // new segment for revised 'Fer'
12114  if(Second == "Fer")
12115  // third has to be a set of IDs separated by spaces, and at least 1
12116  {
12117  FormatType = ExitRailway;
12118  LocationType = EnRoute;
12119  SequenceType = FinishSequence;
12120  ShuttleLinkType = NotAShuttleLink;
12121  if(CheckLocationsExistInRailway)
12122  {
12123  if(!CheckAndPopulateListOfIDs(0, Third, ExitList, GiveMessages))
12124  {
12125  Utilities->CallLogPop(1519);
12126  return(false);
12127  }
12128  }
12129  Utilities->CallLogPop(1520);
12130  return(true);
12131  }
12132  if(Second == "dsc") //new at v2.15.0 - change description
12133  {
12134  if(Third.Length() > 60)
12135  {
12136  TimetableMessage(GiveMessages, "Train description too long, limit of 60 characters in '" + Third + "'");
12137  Utilities->CallLogPop(2582);
12138  return(false);
12139  }
12140  for(int x = 1; x < Third.Length() + 1; x++)
12141  {
12142 // if((Third[x] < ' ') || (Third[x] > '~')) changed at v2.16.0 to allow extended characters in location names
12143  if((Third[x] < ' ') && (Third[x] >= 0))
12144  {
12145  TimetableMessage(GiveMessages, "Train description contains invalid characters in '" + Third + "'");
12146  Utilities->CallLogPop(2583);
12147  return(false);
12148  }
12149  }
12150  FormatType = TimeCmdDescription;
12151  LocationType = AtLocation;
12152  SequenceType = IntermediateSequence;
12153  ShuttleLinkType = NotAShuttleLink;
12154  Utilities->CallLogPop(2604);
12155  return(true);
12156  }
12157 
12158 // if((Second == "fsp") || (Second == "fsp")) then there can optionlly be a fourth: xx-yy where xx = percentage mass & yy = percentage power in the split train
12159 
12160  // all remainder must be TimeCmdHeadCode types to be valid
12161  if((Second != "Fns") && (Second != "Fjo") && (Second != "jbo") && (Second != "fsp") && (Second != "rsp") && (Second != "Sfs") && (Second != "Sns") &&
12162  (Second != "Frh-sh"))
12163  {
12164  Utilities->CallLogPop(922);
12165  return(false); // all TimeCmdHeadCode types
12166  }
12167  if(!CheckHeadCodeValidity(7, GiveMessages, Third))
12168  {
12169  Utilities->CallLogPop(923);
12170  return(false);
12171  }
12172  FormatType = TimeCmdHeadCode;
12173  LocationType = AtLocation;
12174  if(Second == "Frh-sh")
12175  {
12176  ShuttleLinkType = ShuttleLink;
12177  }
12178  else
12179  {
12180  ShuttleLinkType = NotAShuttleLink;
12181  }
12182  if((Second == "Fns") || (Second == "Fjo") || (Second == "Frh-sh"))
12183  {
12184  SequenceType = FinishSequence;
12185  }
12186  if((Second == "jbo") || (Second == "fsp") || (Second == "rsp"))
12187  {
12188  SequenceType = IntermediateSequence;
12189  }
12190  if((Second == "Sfs") || (Second == "Sns"))
12191  {
12192  SequenceType = StartSequence;
12193  }
12194  //new at v2.15.0 to allow splits to have different characteristics - Fourth specifies: AA-BB where AA & BB can be 1 or 2 digits, AA is percentage mass
12195  if((Fourth != "") && ((Second == "fsp") || (Second == "rsp"))) //& BB is percentage power allocated to the split off train
12196  {
12197  if(!CheckFourthValidityForSplit(Fourth, GiveMessages))
12198  {
12199  Utilities->CallLogPop(2584);
12200  return(false);
12201  }
12202  }
12203  Utilities->CallLogPop(924);
12204  return(true);
12205 }
12206 
12207 // ---------------------------------------------------------------------------
12208 
12209 bool TTrainController::CheckFourthValidityForSplit(AnsiString SplitDistributionString, bool GiveMessages) //new at v2.15.0
12210 {
12211  Utilities->CallLog.push_back(Utilities->TimeStamp() + ",CheckFourthValidityForSplit," + SplitDistributionString);
12212  bool ErrorFlag = false;
12213  int x, y;
12214  if((SplitDistributionString.Length() > 6 ) || (SplitDistributionString.Length() < 3))
12215  {
12216  ErrorFlag = true;
12217  }
12218  int pos = SplitDistributionString.Pos('-');
12219  if(pos == 0)
12220  {
12221  ErrorFlag = true;
12222  }
12223  else
12224  {
12225  AnsiString MassStr = SplitDistributionString.SubString(1, pos - 1);
12226  AnsiString PowerStr = SplitDistributionString.SubString(pos + 1, SplitDistributionString.Length() - pos);
12227  try //allows for one or two digit percentages
12228  {
12229  int x = MassStr.ToInt();
12230  int y = PowerStr.ToInt();
12231  if((x > 99) || (x < 1) || (y > 100) || (y < 0))
12232  {
12233  ErrorFlag = true;
12234  }
12235  }
12236  catch(const Exception &e) //non-error catch
12237  {
12238  ErrorFlag = true;
12239  }
12240  }
12241  if(ErrorFlag)
12242  {
12243  TimetableMessage(GiveMessages, "Error in split distribution " + SplitDistributionString + ", should be 'AA-BB' where AA is the percentage mass (min 1, max 99) and BB the percentage " +
12244  "power for the new split-off train");
12245  Utilities->CallLogPop(2585);
12246  return(false);
12247  }
12248  Utilities->CallLogPop(2601);
12249  return(true);
12250 }
12251 
12252 // ---------------------------------------------------------------------------
12253 
12254 bool TTrainController::CheckLocationValidity(int Caller, AnsiString LocStr, bool GiveMessages, bool CheckLocationsExistInRailway)
12255 {
12256  // check that the location name exists in the railway (only if CheckLocationsExistInRailway is true), doesn't begin with 'digit-digit-colon'
12257  // and contains no control characters changed at v2.16.0 to allow extended characters in location names
12258  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckLocationValidity," + LocStr);
12259  if(LocStr == "")
12260  {
12261  Utilities->CallLogPop(1353);
12262  return(false); // has to have at least one character
12263  }
12264 // if((LocStr[1] >= '0') && (LocStr[1] <= '9')) //changed at v2.16.0 to allow locations to begine with digits, if 'digit-digit-colon' then must be a time
12265  if((LocStr.Length() >= 3) && (LocStr[1] >= '0') && (LocStr[1] <= '9') && (LocStr[2] >= '0') && (LocStr[2] <= '9') && (LocStr[3] == ':'))
12266  {
12267  Utilities->CallLogPop(1354);
12268  return(false); // can't begin with 'digit-digit-colon' as this regarded as a time
12269  }
12270  for(int x = 1; x < LocStr.Length() + 1; x++)
12271  {
12272  if(((LocStr[x] < ' ') && (LocStr[x] >= 0)) || (LocStr[x] == ',') || (LocStr[x] == ';')) //changed at v2.16.0 to allow extended characters in location names
12273  {
12274  Utilities->CallLogPop(1355);
12275  return(false); // contains a special character or ',' or ';'
12276  }
12277 /*
12278  if(LocStr[x] > 'z') //dropped at v2.16.0 to allow extended characters in location names
12279  {
12280  Utilities->CallLogPop(1356);
12281  return(false); // contains a character outside the standard ASCII set
12282  }
12283 */
12284  }
12285  // check exists in railway location list if CheckLocationsExistInRailway is true
12286  if(CheckLocationsExistInRailway)
12287  {
12288  if(!Track->TimetabledLocationNameAllocated(3, LocStr))
12289  {
12290  TimetableMessage(GiveMessages, "Location name '" + LocStr +
12291  "' appears in the timetable but is not a valid name. To be valid the name must be a stopping location and apply to one or more platforms " +
12292  "(not concourses on their own), or to track at a blue non-station named location. BUT NOTE THAT trains can't stop at continuations so a name " +
12293  "that includes a continuation will not be valid.");
12294  Utilities->CallLogPop(1357);
12295  return(false);
12296  }
12297  }
12298  Utilities->CallLogPop(1358);
12299  return(true);
12300 }
12301 
12302 // ---------------------------------------------------------------------------
12303 
12304 bool TTrainController::CheckHeadCodeValidity(int Caller, bool GiveMessages, AnsiString HeadCode)
12305 {
12306  // if(!AnyHeadCodeValid) up to 8 characters total & last 4 characters must be NLNN where N = number and L = capital or small letter
12307  // if(AnyHeadCodeValid) up to 8 characters total, last 2 chars must be digits & last but 2 can be any alphanumeric, upper or lower case
12308  // NOTE: As of v0.6b AnyHeadCodeValid dropped, all headcodes are unrestricted
12309  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString((short)GiveMessages) + ",CheckHeadCodeValidity," +
12310  HeadCode);
12311  if((HeadCode.Length() < 4) || (HeadCode.Length() > 8))
12312  {
12313  TimetableMessage(GiveMessages, "Headcode error in '" + HeadCode +
12314  "', length must be between 4 and 8 characters, and last 4 must be a legitimate headcode. This error can also be caused by omitting a service reference after Sns, Snt-sh, Sns-sh, Fns, Fns-sh or Frh-sh");
12315  Utilities->CallLogPop(1359);
12316  return(false);
12317  }
12318  // firstly allow any printable character (ASCII >= CHAR(32) & <= CHAR(126)), as these allowed in 1st 4 characters
12319  for(int x = 1; x < (HeadCode.Length() + 1); x++)
12320  {
12321  if((HeadCode[x] < ' ') || (HeadCode[x] > '~'))
12322  {
12323  TimetableMessage(GiveMessages, "Non-printable character in headcode '" + HeadCode + "'");
12324  Utilities->CallLogPop(1895);
12325  return(false);
12326  }
12327  }
12328  // secondly ensure the true Headcode only has letters or digits
12329  for(int x = 3; x >= 0; x--)
12330  {
12331  if(((HeadCode[HeadCode.Length() - x] < 'A') || (HeadCode[HeadCode.Length() - x] > 'Z')) && ((HeadCode[HeadCode.Length() - x] < 'a') ||
12332  (HeadCode[HeadCode.Length() - x] > 'z')) && ((HeadCode[HeadCode.Length() - x] < '0') || (HeadCode[HeadCode.Length() - x] > '9')))
12333  {
12334  TimetableMessage(GiveMessages, "Headcode error in '" + HeadCode + "', headcode must consist of letters and digits only");
12335  Utilities->CallLogPop(1790);
12336  return(false);
12337  }
12338  }
12339  Utilities->CallLogPop(1364);
12340  return(true);
12341 }
12342 
12343 // ---------------------------------------------------------------------------
12344 
12345 bool TTrainController::CheckAndPopulateListOfIDs(int Caller, AnsiString IDSet, TNumList &ExitList, bool GiveMessages)
12346 // set of track element IDs, separated by spaces, and at least 1 present
12347 {
12348  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckAndPopulateListOfIDs," + IDSet); //had wrong title, changed at v2.13.0
12349  ExitList.clear();
12350  AnsiString CurrentID = "";
12351 
12352  if(IDSet.Length() == 0)
12353  {
12354  TimetableMessage(GiveMessages, "Must have at least one exit element ID following 'Fer'");
12355  Utilities->CallLogPop(1521);
12356  return(false);
12357  }
12358  for(int x = 1; x <= IDSet.Length(); x++)
12359  {
12360  char C = IDSet[x];
12361  if(((C < '0') || (C > '9')) && (C != ' ') && (C != 'N') && (C != '-'))
12362  {
12363  TimetableMessage(GiveMessages, "Illegal character in the set of element IDs following 'Fer' in '" + IDSet + "'");
12364  Utilities->CallLogPop(1522);
12365  return(false);
12366  }
12367 /* don't use, error checks in GetTrackVectorPositionFromString instead
12368  if(C == '-') //this section added at v2.13.0 because of Amon Sadler's error file submitted 24/03/22
12369  {
12370  if((x==1) || (x == IDSet.Length()))
12371  {
12372  TimetableMessage(GiveMessages, "Illegal minus character ('-') in the set of element IDs following 'Fer' in '" + IDSet + "'");
12373  Utilities->CallLogPop(2479);
12374  return(false);
12375  }
12376  if((IDSet[x-1] < '0') || (IDSet[x-1] > '9') || (IDSet[x+1] < '0') || (IDSet[x+1] > '9'))
12377  {
12378  TimetableMessage(GiveMessages, "Illegal minus character ('-') in the set of element IDs following 'Fer' in '" + IDSet + "'");
12379  Utilities->CallLogPop(2480);
12380  return(false);
12381  }
12382  }
12383 */
12384  }
12385  int Pos = IDSet.Pos(' '); // look for the first space
12386 
12387  while(true)
12388  {
12389  if(Pos == 0)
12390  {
12391  CurrentID = IDSet;
12392  IDSet = "";
12393  }
12394  else
12395  {
12396  CurrentID = IDSet.SubString(1, Pos - 1);
12397  IDSet = IDSet.SubString(Pos + 1, IDSet.Length() - Pos);
12398  }
12399  int VecPos = Track->GetTrackVectorPositionFromString(7, CurrentID, GiveMessages);
12400  if(VecPos == -1)
12401  {
12402  Utilities->CallLogPop(1523);
12403  return(false); // messages given in GetTrackVectorPositionFromString
12404  }
12405  else
12406  {
12407  if(Track->TrackElementAt(722, VecPos).TrackType != Continuation)
12408  {
12409  TimetableMessage(GiveMessages, "The element ID '" + CurrentID + "' following 'Fer' is not an exit");
12410  Utilities->CallLogPop(1524);
12411  return(false);
12412  }
12413  else
12414  {
12415  // first check for duplicates
12416  if(!ExitList.empty())
12417  {
12418  for(TNumListIterator ELIT = ExitList.begin(); ELIT != ExitList.end(); ELIT++)
12419  {
12420  if(*ELIT == VecPos)
12421  {
12422  TimetableMessage(GiveMessages, "The element ID '" + CurrentID + "' following 'Fer' duplicates an earlier element");
12423  Utilities->CallLogPop(1532);
12424  return(false);
12425  }
12426  }
12427  }
12428  // of OK add it to the list
12429  ExitList.push_back(VecPos);
12430  }
12431  }
12432  if(IDSet == "")
12433  {
12434  Utilities->CallLogPop(1525);
12435  return(true);
12436  }
12437  else
12438  {
12439  Pos = IDSet.Pos(' '); // look for the next space
12440  }
12441  } // while(true)
12442 }
12443 
12444 // ---------------------------------------------------------------------------
12445 bool TTrainController::SplitTrainInfo(int Caller, AnsiString TrainInfoStr, AnsiString &HeadCode, AnsiString &Description, int &StartSpeed, int &MaxRunningSpeed,
12446  int &Mass, double &MaxBrakeRate, double &PowerAtRail, int &SignallerSpeed, bool GiveMessages)
12447 // 7 or 8 items for a new train (6 or 7 semicolons), for a continuing service only need headcode, though can have a description, if other
12448 // data entered for continuing service then will be ignored - message given to warn user, checks appropriate number of items and validity
12449 // of each item
12450 {
12451  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SplitTrainInfo," + TrainInfoStr);
12452  int Pos = 0;
12453  AnsiString Remainder = "";
12454  int SemiColonCount = 0;
12455 
12456  for(int x = 1; x < TrainInfoStr.Length() + 1; x++)
12457  {
12458  if(TrainInfoStr[x] == ';')
12459  {
12460  SemiColonCount++;
12461  }
12462  }
12463  if((SemiColonCount != 6) && (SemiColonCount != 7) && (SemiColonCount != 1) && (SemiColonCount != 0))
12464  {
12465  TimetableMessage(GiveMessages, "Error in train information in '" + TrainInfoStr +
12466  "'. Should be headcode + optional description for a continuing service;" +
12467  " or headcode, description, start speed, max running speed, mass, brake force, power (and optional signaller max. speed) for a new service");
12468  Utilities->CallLogPop(880);
12469  return(false);
12470  }
12471  if(SemiColonCount == 0)
12472  {
12473  HeadCode = TrainInfoStr;
12474  if(!CheckHeadCodeValidity(8, GiveMessages, HeadCode))
12475  {
12476  Utilities->CallLogPop(881);
12477  return(false);
12478  }
12479  Utilities->CallLogPop(882);
12480  return(true);
12481  }
12482  if(SemiColonCount == 1) // headcode & description only
12483  {
12484  Pos = TrainInfoStr.Pos(';'); // 1st delimiter
12485  HeadCode = TrainInfoStr.SubString(1, Pos - 1);
12486  Description = TrainInfoStr.SubString(Pos + 1, TrainInfoStr.Length() - Pos);
12487  if(!CheckHeadCodeValidity(9, GiveMessages, HeadCode))
12488  {
12489  Utilities->CallLogPop(883);
12490  return(false);
12491  }
12492  if(Description == "")
12493  {
12494  TimetableMessage(GiveMessages, "Train description missing in '" + TrainInfoStr + "'");
12495  Utilities->CallLogPop(884);
12496  return(false);
12497  }
12498  if(Description.Length() > 60)
12499  {
12500  TimetableMessage(GiveMessages, "Train description too long, limit of 60 characters '" + TrainInfoStr + "'");
12501  Utilities->CallLogPop(1157);
12502  return(false);
12503  }
12504  for(int x = 1; x < Description.Length() + 1; x++)
12505  {
12506 // if((Description[x] < ' ') || (Description[x] > '~')) changed at v2.16.0 to allow extended characters in location names
12507  if((Description[x] < ' ') && (Description[x] >= 0))
12508  {
12509  TimetableMessage(GiveMessages, "Train description contains invalid characters in '" + TrainInfoStr + "'");
12510  Utilities->CallLogPop(885);
12511  return(false);
12512  }
12513  }
12514  Utilities->CallLogPop(886);
12515  return(true);
12516  }
12517  // if here must have 6 or 7 semicolons
12518  Pos = TrainInfoStr.Pos(';'); // 1st delimiter
12519  HeadCode = TrainInfoStr.SubString(1, Pos - 1);
12520  Remainder = TrainInfoStr.SubString(Pos + 1, TrainInfoStr.Length() - Pos);
12521  if(!CheckHeadCodeValidity(10, GiveMessages, HeadCode))
12522  {
12523  Utilities->CallLogPop(887);
12524  return(false);
12525  }
12526  Pos = Remainder.Pos(';'); // 2nd delimiter
12527  Description = Remainder.SubString(1, Pos - 1);
12528  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
12529  if(Description == "")
12530  {
12531  TimetableMessage(GiveMessages, "Train description missing in '" + TrainInfoStr + "'");
12532  Utilities->CallLogPop(888);
12533  return(false);
12534  }
12535  if(Description.Length() > 60)
12536  {
12537  TimetableMessage(GiveMessages, "Train description too long, limit of 60 characters '" + TrainInfoStr + "'");
12538  Utilities->CallLogPop(1158);
12539  return(false);
12540  }
12541  for(int x = 1; x < Description.Length() + 1; x++)
12542  {
12543 // if((Description[x] < ' ') || (Description[x] > 126)) changed at v2.16.0 to allow extended characters in location names
12544  if((Description[x] < ' ') && (Description[x] >= 0))
12545  {
12546  TimetableMessage(GiveMessages, "Train description contains invalid characters in '" + TrainInfoStr + "'");
12547  Utilities->CallLogPop(889);
12548  return(false);
12549  }
12550  }
12551  Pos = Remainder.Pos(';'); // 3rd delimiter
12552  AnsiString StartSpeedStr = Remainder.SubString(1, Pos - 1);
12553 
12554  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
12555  if(StartSpeedStr == "")
12556  {
12557  TimetableMessage(GiveMessages, "Train starting speed missing in '" + TrainInfoStr + "'");
12558  Utilities->CallLogPop(890);
12559  return(false);
12560  }
12561  for(int x = 1; x < StartSpeedStr.Length() + 1; x++)
12562  {
12563  if((StartSpeedStr[x] < '0') || (StartSpeedStr[x] > '9'))
12564  {
12565  TimetableMessage(GiveMessages, "Train start speed contains invalid characters in '" + TrainInfoStr + "'");
12566  Utilities->CallLogPop(891);
12567  return(false);
12568  }
12569  }
12570  StartSpeed = StartSpeedStr.ToInt();
12571  if(StartSpeed > TTrain::MaximumSpeedLimit) // 400kph = 250mph
12572  {
12573  StartSpeed = TTrain::MaximumSpeedLimit;
12574  if(!SSHigh) // added at v2.4.0
12575  {
12576  TimetableMessage(GiveMessages, "Train starting speed > 400km/h in '" + TrainInfoStr + "'. Setting it to 400km/h");
12577  SSHigh = true;
12578  }
12579  }
12580  Pos = Remainder.Pos(';'); // 4th delimiter
12581  AnsiString MaxRunningSpeedStr = Remainder.SubString(1, Pos - 1);
12582 
12583  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
12584  if(MaxRunningSpeedStr == "")
12585  {
12586  TimetableMessage(GiveMessages, "Train maximum running speed missing in '" + TrainInfoStr + "'");
12587  Utilities->CallLogPop(892);
12588  return(false);
12589  }
12590  for(int x = 1; x < MaxRunningSpeedStr.Length() + 1; x++)
12591  {
12592  if((MaxRunningSpeedStr[x] < '0') || (MaxRunningSpeedStr[x] > '9'))
12593  {
12594  TimetableMessage(GiveMessages, "Train maximum running speed contains invalid characters in '" + TrainInfoStr + "'");
12595  Utilities->CallLogPop(893);
12596  return(false);
12597  }
12598  }
12599  MaxRunningSpeed = MaxRunningSpeedStr.ToInt();
12600  if(MaxRunningSpeed > TTrain::MaximumSpeedLimit) // 400kph = 250mph
12601  {
12602  MaxRunningSpeed = TTrain::MaximumSpeedLimit;
12603  if(!MRSHigh) // added at v2.4.0
12604  {
12605  TimetableMessage(GiveMessages, "Train maximum running speed > 400km/h in '" + TrainInfoStr + "'. Setting it to 400km/h");
12606  MRSHigh = true;
12607  }
12608  }
12609  if(MaxRunningSpeed < 10)
12610  // changed at v0.6 to prevent low max speeds - can cause problems in SetTrainMovementValues
12611  {
12612  MaxRunningSpeed = 10;
12613  if(!MRSLow) // added at v2.4.0
12614  {
12615  TimetableMessage(GiveMessages, "Train maximum running speed can't be less than 10km/h in '" + TrainInfoStr + "', it will be set to 10km/h");
12616  MRSLow = true;
12617  }
12618  }
12619  Pos = Remainder.Pos(';'); // 5th delimiter
12620  AnsiString MassStr = Remainder.SubString(1, Pos - 1);
12621 
12622  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
12623  if(MassStr == "")
12624  {
12625  TimetableMessage(GiveMessages, "Train mass missing in '" + TrainInfoStr + "'");
12626  Utilities->CallLogPop(895);
12627  return(false);
12628  }
12629  for(int x = 1; x < MassStr.Length() + 1; x++)
12630  {
12631  if((MassStr[x] < '0') || (MassStr[x] > '9'))
12632  {
12633  TimetableMessage(GiveMessages, "Train mass contains invalid characters in '" + TrainInfoStr + "'");
12634  Utilities->CallLogPop(896);
12635  return(false);
12636  }
12637  }
12638  Mass = MassStr.ToInt() * 1000; // convert tonnes to kg
12639  if(Mass > TTrain::MaximumMassLimit) // 10,000tonnes
12640  {
12641  Mass = TTrain::MaximumMassLimit;
12642  if(!MassHigh) // added at v2.4.0
12643  {
12644  TimetableMessage(GiveMessages, "Train mass > 10,000 tonnes in '" + TrainInfoStr + "'. Setting it to 10,000 tonnes");
12645  MassHigh = true;
12646  }
12647  }
12648  if(Mass == 0)
12649  {
12650  TimetableMessage(GiveMessages, "Train mass zero in '" + TrainInfoStr + "'");
12651  Utilities->CallLogPop(897);
12652  return(false);
12653  }
12654  Pos = Remainder.Pos(';'); // 6th delimiter
12655  AnsiString MaxBrakeForceStr = Remainder.SubString(1, Pos - 1);
12656 
12657  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
12658  if(MaxBrakeForceStr == "")
12659  {
12660  TimetableMessage(GiveMessages, "Train braking force missing in '" + TrainInfoStr + "'");
12661  Utilities->CallLogPop(898);
12662  return(false);
12663  }
12664  for(int x = 1; x < (MaxBrakeForceStr.Length() + 1); x++)
12665  {
12666  if((MaxBrakeForceStr[x] != '.') && ((MaxBrakeForceStr[x] < '0') || (MaxBrakeForceStr[x] > '9')))
12667  {
12668  TimetableMessage(GiveMessages, "Train braking force contains invalid characters in '" + TrainInfoStr + "'");
12669  Utilities->CallLogPop(899);
12670  return(false);
12671  }
12672  }
12673  double MaxBrakeForce = MaxBrakeForceStr.ToDouble() * 1000;
12674 
12675  // convert to kg force
12676  if((MaxBrakeForce / Mass) > 1) // gives 'g' braking - 9.81m/s/s
12677  {
12678  MaxBrakeForce = Mass;
12679  if(!BFHigh) // added at v2.4.0
12680  {
12681  TimetableMessage(GiveMessages, "Train braking force too high in '" + TrainInfoStr + "'. Setting it to the same as the train mass");
12682  BFHigh = true;
12683  }
12684  }
12685  if((MaxBrakeForce / Mass) < 0.01)
12686  {
12687  MaxBrakeForce = Mass * 0.01;
12688  if(!BFLow) // added at v2.4.0
12689  {
12690  TimetableMessage(GiveMessages, "Train braking force too low in '" + TrainInfoStr + "'. Setting it to 1% of the train mass");
12691  BFLow = true;
12692  }
12693  }
12694  // convert to m/s/s
12695  MaxBrakeRate = MaxBrakeForce / Mass * 9.81;
12696  // now may have just a power entry or power and signaller max. speed
12697  AnsiString GrossPowerStr = "", SignallerSpeedStr = "";
12698 
12699  if(SemiColonCount == 6)
12700  {
12701  GrossPowerStr = Remainder;
12702  SignallerSpeedStr = "30"; // default value
12703  }
12704  else // must be 7
12705  {
12706  Pos = Remainder.Pos(';'); // 7th delimiter
12707  GrossPowerStr = Remainder.SubString(1, Pos - 1);
12708  SignallerSpeedStr = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
12709  }
12710  // deal with GrossPower
12711  if(GrossPowerStr == "")
12712  {
12713  TimetableMessage(GiveMessages, "Train power missing in '" + TrainInfoStr + "'");
12714  Utilities->CallLogPop(901);
12715  return(false);
12716  }
12717  for(int x = 1; x < GrossPowerStr.Length() + 1; x++)
12718  {
12719  if((GrossPowerStr[x] < '0') || (GrossPowerStr[x] > '9'))
12720  {
12721  TimetableMessage(GiveMessages, "Train power contains invalid characters in '" + TrainInfoStr + "'");
12722  Utilities->CallLogPop(902);
12723  return(false);
12724  }
12725  }
12726 
12727  double GrossPower = GrossPowerStr.ToInt() * 1000; // convert to W
12728 
12729  if(GrossPower > TTrain::MaximumPowerLimit) // 100MW
12730  {
12731  GrossPower = TTrain::MaximumPowerLimit;
12732  if(!PwrHigh)
12733  {
12734  TimetableMessage(GiveMessages, "Train power > 100,000kW in '" + TrainInfoStr + "'. Setting it to 100,000kW");
12735  PwrHigh = true;
12736  }
12737  }
12738  else if(GrossPower == 0) // changed at v2.4.0
12739  {
12740  GrossPower = 0.1;
12741  // can't be zero or AValue is zero and then have divide by zero error, so set to 0.1W so acceleration tiny (though should be intercepted before accel calculated)
12742  }
12743  else if((GrossPower > 0) && (GrossPower < 10000))
12744  // added at v2.4.0 to ensure min power of 8kW at rail unless zero (otherwise could have too low AValues
12745  {
12746  GrossPower = 10000;
12747  }
12748  PowerAtRail = GrossPower * 0.8;
12749  // apply ratio of 80% for rail to gross power (seems about average from an internet search)
12750 
12751  // deal with SignallerSpeed
12752  if(SignallerSpeedStr == "")
12753  {
12754  TimetableMessage(GiveMessages, "Signaller speed not set in '" + TrainInfoStr + "', either set a value or remove the extra semicolon");
12755  Utilities->CallLogPop(1771);
12756  return(false);
12757  }
12758  for(int x = 1; x < SignallerSpeedStr.Length() + 1; x++)
12759  {
12760  if((SignallerSpeedStr[x] < '0') || (SignallerSpeedStr[x] > '9'))
12761  {
12762  TimetableMessage(GiveMessages, "Signaller speed contains invalid characters in '" + TrainInfoStr + "'");
12763  Utilities->CallLogPop(1769);
12764  return(false);
12765  }
12766  }
12767  SignallerSpeed = SignallerSpeedStr.ToInt();
12768  if(SignallerSpeed > TTrain::MaximumSpeedLimit)
12769  {
12770  SignallerSpeed = TTrain::MaximumSpeedLimit;
12771  if(!SigSHigh)
12772  {
12773  TimetableMessage(GiveMessages, "Signaller speed > 400km/h in '" + TrainInfoStr + "'. Setting it to 400km/h");
12774  SigSHigh = true;
12775  }
12776  }
12777  if(SignallerSpeed < 10)
12778  // changed at v0.6 to prevent low max speeds - can cause problems in SetTrainMovementValues
12779  {
12780  SignallerSpeed = 10;
12781  if(!SigSLow)
12782  {
12783  TimetableMessage(GiveMessages, "Signaller speed can't be less than 10km/h in '" + TrainInfoStr + "', it will be set to 10km/h");
12784  SigSLow = true;
12785  }
12786  // Utilities->CallLogPop(1770);
12787  // return false;
12788  }
12789  Utilities->CallLogPop(904);
12790  return(true);
12791 }
12792 
12793 // ---------------------------------------------------------------------------
12794 
12795 bool TTrainController::SplitRepeat(int Caller, AnsiString OneEntry, int &RearStartOrRepeatMins, int &FrontStartOrRepeatDigits, int &NumberOfRepeats,
12796  bool GiveMessages)
12797 {
12798  // Format must be: R;mm;dd;nn mm may be 1, 2 or more digits, dd may be 1 or 2 digits, nn may be 1, 2 or more digits
12799  // function checks validity of each item and returns false for error
12800  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SplitRepeat," + OneEntry);
12801  if(OneEntry.Length() < 7)
12802  {
12803  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - should be 'R;m;d;n'");
12804  Utilities->CallLogPop(865);
12805  return(false);
12806  }
12807  int SemiColonCount = 0;
12808 
12809  for(int x = 1; x < OneEntry.Length() + 1; x++)
12810  {
12811  if(OneEntry[x] == ';')
12812  {
12813  SemiColonCount++;
12814  }
12815  }
12816  if(SemiColonCount != 3)
12817  {
12818  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - should be 'R;m;d;n'");
12819  Utilities->CallLogPop(866);
12820  return(false);
12821  }
12822  if((OneEntry[1] != 'R') || (OneEntry[2] != ';'))
12823  {
12824  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - should be 'R;m;d;n'");
12825  Utilities->CallLogPop(867);
12826  return(false);
12827  }
12828  AnsiString Remainder = OneEntry.SubString(3, OneEntry.Length() - 2);
12829  // strip off R;
12830 
12831  int Pos = 0;
12832 
12833  Pos = Remainder.Pos(';');
12834  AnsiString MinutesStr = Remainder.SubString(1, Pos - 1);
12835 
12836  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
12837  if(MinutesStr == "")
12838  {
12839  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - minute increment segment missing");
12840  Utilities->CallLogPop(868);
12841  return(false);
12842  }
12843  if(MinutesStr.Length() > 3)
12844  // added for v2.3.1 following Albie Vowles' reported error in repeat value 03/02/20
12845  {
12846  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - minute value too high, maximum value is 999");
12847  Utilities->CallLogPop(2119);
12848  return(false);
12849  }
12850  for(int x = 1; x < MinutesStr.Length() + 1; x++)
12851  {
12852  if((MinutesStr[x] < '0') || (MinutesStr[x] > '9'))
12853  {
12854  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - non-digit character in minute increment segment");
12855  Utilities->CallLogPop(869);
12856  return(false);
12857  }
12858  }
12859  RearStartOrRepeatMins = MinutesStr.ToInt();
12860  if(RearStartOrRepeatMins == 0)
12861  {
12862  TimetableMessage(GiveMessages, "Repeat minute increment is zero in: '" + OneEntry + "' - can't have a zero value");
12863  Utilities->CallLogPop(870);
12864  return(false);
12865  }
12866  Pos = Remainder.Pos(';');
12867  AnsiString DigitsStr = Remainder.SubString(1, Pos - 1);
12868 
12869  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
12870  if(DigitsStr == "")
12871  {
12872  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - headcode increment segment missing");
12873  Utilities->CallLogPop(871);
12874  return(false);
12875  }
12876  for(int x = 1; x < DigitsStr.Length() + 1; x++)
12877  {
12878  if((DigitsStr[x] < '0') || (DigitsStr[x] > '9'))
12879  {
12880  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - non-digit character in headcode increment segment");
12881  Utilities->CallLogPop(872);
12882  return(false);
12883  }
12884  }
12885  if(DigitsStr.Length() > 2)
12886  {
12887  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - maximum number of digits for headcode increment is 2");
12888  Utilities->CallLogPop(873);
12889  return(false);
12890  }
12891  FrontStartOrRepeatDigits = DigitsStr.ToInt();
12892 /* allow zero digit increments so HC can stay same for repeated services - for many suburban services the headcode digits relate to the
12893  route rather than the service
12894  if(FrontStartOrRepeatDigits == 0)
12895  {
12896  TimetableMessage(GiveMessages, "Repeat headcode increment is zero in: '" + OneEntry + "' - can't have a zero value");
12897  Utilities->CallLogPop(874);
12898  return false;
12899  }
12900 */
12901  if(!Last2CharactersBothDigits(0, ServiceReference) && (FrontStartOrRepeatDigits > 0))
12902  // new for v0.6b for unrestricted headcodes
12903  {
12904  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry +
12905  "' - a repeating service with incrementing digits must have digits as its last two headcode characters");
12906  Utilities->CallLogPop(1889);
12907  return(false);
12908  }
12909  AnsiString NumberStr = Remainder;
12910 
12911  if(NumberStr == "")
12912  {
12913  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - number of repeats missing");
12914  Utilities->CallLogPop(875);
12915  return(false);
12916  }
12917  if(NumberStr.Length() > 4)
12918  // added for v2.3.1 following Albie Vowles' reported error 03/02/20
12919  {
12920  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - repeat value too high, no timetabled event can exceed 95 hours & 59 minutes");
12921  Utilities->CallLogPop(2118);
12922  return(false);
12923  }
12924  for(int x = 1; x < NumberStr.Length() + 1; x++)
12925  {
12926  if((NumberStr[x] < '0') || (NumberStr[x] > '9'))
12927  // catches negative numbers
12928  {
12929  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - non-digit character in number of repeats");
12930  Utilities->CallLogPop(876);
12931  return(false);
12932  }
12933  }
12934  NumberOfRepeats = NumberStr.ToInt();
12935  if(NumberOfRepeats == 0)
12936  {
12937  TimetableMessage(GiveMessages, "Number of repeats is zero in: '" + OneEntry + "' - if no repeats are needed the repeat should be omitted");
12938  Utilities->CallLogPop(877);
12939  return(false);
12940  }
12941  Utilities->CallLogPop(878);
12942  return(true);
12943 }
12944 
12945 // ---------------------------------------------------------------------------
12946 
12947 bool TTrainController::SecondPassActions(int Caller, bool GiveMessages, bool &TwoLocationFlag) //TwoLocationFlag added at v2.9.1
12948 /* Note that here the TrainDataVector has been compiled with FinalCall true in ProcessOneTimetableLine so work on the
12949  vector rather than the timetable
12950  Note also that for unlocated Snt entries the LocationType hasn't yet been set
12951 
12952  Many of the errors caught here duplicate those in the preliminary checks, but leave in for completeness
12953 
12954 Updated significantly for v2.15.0. Current procedure:-
12955 
12956 Preliminary checks for v0.2b without changing anything, carry each out separately:-
12957  1) must have at least one actionvector entry
12958  2) if first actionvector entry not SignallerControl then must have at least one more actionvector entry
12959  3) if first actionvector entry is SignallerControl then must have no more actionvector entries except a repeat
12960  4) first entry must be a start;
12961  4a) if first entry is Snt and second is a finish then it can't be Fns-sh or Frh-sh
12962  4b) if first entry is Sns or Sfs and second is a finish then it must be either Frh or Fjo
12963  4c) if first entry is Snt-sh, Sns-sh or Sns-fsh second can't be a finish
12964  5) a start must be the first entry;
12965  6) a repeat entry must be the last;
12966  7) for other than SignallerControl the last entry must be repeat or finish; if last entry is a repeat the last but one must be a finish;
12967  8) a finish entry must be the last or last but one, and if last but one the last must be a repeat
12968  Other successor errors will be caught later as all 'throws' changed to messages prior to the bulk of the sucessor checks
12969 
12970 Set location for located Snt or Snt-sh and ensure successor AtLocation
12971 For unlocated Snt-sh give error message
12972 For unlocated Snt & not sig control check successor moving
12973 
12974 Check all other starts (all located) have valid successors
12975 
12976 Set location for Sns-sh and Sns-fsh from following TimeLoc, if not one then give message
12977 
12978 Carry out linkage checks to ensure all links present, no data set yet & locations not checked yet. First check for duplicates, then for cross
12979 references, then for non-repeating shuttle cross refs. This is done because the later location naming functions give error messages if there
12980 are missing links.
12981 
12982 Set names for all Fns finishes from earlier named event or fail if can't find
12983 Set names for linked Sns events with same event times from above, but first carry out immediate successor checks and give error message for:-
12984 no successors, moving successor, another start sequence, a finish that isn't Frh or Fjo or a repeat. No error messages given here for location
12985 not found, that check done later.
12986 
12987 Trap errors where rsp/fsp follows an Sfs without a TimeLoc arrival before (or unlikely to be able to set fsp/rsp/Sfs location because Sfs locs set
12988 from linked fsp/rsp events)
12989 
12990 Name all fsp/rsp events, then check that all named or give error message.
12991 
12992 Set all Sfs names from above fsp/rsp links with same event times, but first carry out immediate successor checks and give error message for:-
12993 no successors, moving successor, another start sequence, a finish that isn't Frh or Fjo or a repeat. No error messages given here for location
12994 not found, that check done later.
12995 
12996 Set remaining AtLoc Command locations from preceding named event
12997 
12998 All location names should now be set
12999 
13000 Final detailed check of names for all AtLoc Commands. If find any without a name give an error message:-
13001 If jbo, fsp, rsp, cdt or dsc say must be preceded by a named event at same location, normally an arrival
13002 If Sns or Sfs say to make sure the linked finish event is preceded by a named event at same location, normally an arrival
13003 If Snt-sh say to make sure that the service starts with zero speed and is at a named location
13004 If Sns-fsh or Sns-sh say to make sure that the event is followed (not necessarily immediately) by a departure
13005 If Frh, Fns, Fjo, Frh-sh, Fns-sh or F-nshs say that the event must be preceded by an event at the same location that has an identified location name,
13006 normally an arrival.
13007 Missing: pas & Fer not AtLoc, Snt whether located or not covered in detail earlier.
13008 
13009 Later checks as before 2.15.0 changes:-
13010 
13011 Check remaining successor validity except for TimeLoc arr & dep since those times not set yet
13012 
13013 Set arrival & departure times for TimeLocs & set their EventTimes to -1 (up to now all have times as EventTime)
13014 
13015 Perform remaining successor checks for TimeLocs
13016 
13017 Check all TimeLocs have either Arr or Dep time set and EventTime == -1, all Cmds have EventTime set & Arr & Dep times == -1, & repeats have no times
13018 set
13019 
13020 Check times stay same or increase through a service, note that can have time of 0 if include midnight
13021 
13022 Check locations consistent
13023 
13024 Check same location doesn't appear twice before a cdt except for separate arr & dep TimeLocs (just a potential error warning given in v2.6.0) i.e.
13025 same location can appear in any number of consecutive entries but once changed couldn't repeat before a direction change prior to v2.6.0. Message
13026 given in InterfaceUnit
13027 
13028 Check all locations except unlocated 'Snt' & 'Fer' have LocationName set and throw error if not.
13029 
13030 Carry out full cross reference and duplicate link checks for all services inc shuttles, and set data and check location consistency
13031 
13032 Check that each shuttle start ends either in Fns or Fxx-sh (though a single service can't end in Fxx-sh), and that
13033 when the Fxx-sh is reached it references the original start and not another shuttle - not allowed to link two shuttles, don't ever need to and as
13034 designed would skip repeats
13035 
13036 Check all entries have all types set to something and throw error if not
13037 
13038 All OK if reach here, so set up the TrainOperatingDataVector (already has one entry) & NumberOfTrains
13039 
13040 Check that don't include any Continuation names
13041 
13042 Check that all repeat times below 96h
13043 
13044 Now that all set up change any extended headcodes back to ordinary headcodes (had been service references until now.
13045 
13046 Finally call BuildContinuationTrainExpectationMultiMap
13047 
13048 ***********************************
13049 
13050 For info:-
13051 class TActionVectorEntry //contains a single train action - repeat entry is also of this class though no train action is taken for it
13052 {
13053 public:
13054 AnsiString LocationName, Command, OtherHeadCode, NonRepeatingShuttleLinkHeadCode; ///< string values for timetabled event entries, null
13056 bool SignallerControl; ///< indicates a train that is defined by the timetable as under signaller control
13057 bool Warning; ///< if set triggers an alert in the warning panel when the action is reached
13058 int NumberOfRepeats; ///< the number of repeating services
13059 int RearStartOrRepeatMins, FrontStartOrRepeatDigits; ///< dual-purpose variables used for the TrackVectorPositions of the rear and front
13061 TDateTime EventTime, ArrivalTime, DepartureTime; ///< relevant times at which the action is timetabled, zeroed on creation so change
13063 TNumList ExitList; ///< the list of valid train exit TrackVector positions for 'Fer' entries (empty to begin with)
13064 TTimetableFormatType FormatType; ///< defines the timetable action type
13065 TTimetableLocationType LocationType; ///< indicates where the train is when the relevant action occurs
13066 TTimetableSequenceType SequenceType; ///< indicates where in the sequence of codes the action lies
13067 TTimetableShuttleLinkType ShuttleLinkType; ///< indicates whether or not the action relates to a shuttle service link
13068 TTrainDataEntry *LinkedTrainEntryPtr; ///< link pointer for use between fsp/rsp & Sfs; Fjo & jbo; Fns & Sns; & all shuttle to shuttle
13070 TTrainDataEntry *NonRepeatingShuttleLinkEntryPtr; ///< pointer used by shuttles for the non-shuttle train links, in & out, the
13072 
13073 // inline function
13074 
13076 TActionVectorEntry() {
13077 RearStartOrRepeatMins=0; FrontStartOrRepeatDigits=0; NumberOfRepeats=0; FormatType=NoFormat;
13078 SequenceType=NoSequence; LocationType=NoLocation; ShuttleLinkType=NoShuttleLink, EventTime=TDateTime(-1);
13079 ArrivalTime=TDateTime(-1); DepartureTime=TDateTime(-1); LinkedTrainEntryPtr=0; NonRepeatingShuttleLinkEntryPtr=0;
13080 Warning = false; SignallerControl = false;
13081 }
13082 };
13083 
13084 typedef std::vector<TActionVectorEntry> TActionVector;//contains all actions for a single train
13085 
13086 class TTrainDataEntry //contains all data for a single train - copied into train object when becomes active
13087 {
13088 public:
13089 AnsiString HeadCode, ServiceReference, Description; ///< headcode is the first train's headcode, rest are calculated from repeat
13092 double MaxBrakeRate; ///< in metres/sec/sec
13093 double MaxRunningSpeed; ///< in km/h
13094 double PowerAtRail; ///< in Watts (taken as 80% of the train's Gross Power, i.e. that entered by the user)
13095 int Mass; ///< in kg
13096 int NumberOfTrains; ///< number of repeats + 1
13097 int SignallerSpeed; ///< in km/h for use when under signaller control
13098 int StartSpeed; ///< in km/h
13099 TActionVector ActionVector; ///< all the actions for the train
13100 TTrainOperatingDataVector TrainOperatingDataVector; ///< operating information for the train including all its repeats
13101 
13102 //inline function
13103 
13105 TTrainDataEntry()
13106 {
13107 StartSpeed=0; MaxRunningSpeed=0; NumberOfTrains=0;
13108 }
13109 };
13110 
13111 Allowable successors:-
13112 Snt unlocated -> Fer, TimeLoc (arr), TimeTimeLoc, (new) pas; No others
13113 Snt located -> No starts, no finishes except Frh, Fjo (as of v2.0.0), Fns, and F-nshs, no repeat, pas, TimeTimeLoc or TimeLoc arr;
13114 any other cmd or TimeLoc (dep) OK
13115 Snt-sh -> No starts, finishes, repeats, pas or TimeTimeLoc; any other cmd or TimeLoc (dep) OK
13116 Sfs -> No starts, finishes except Frh & Fjo (as of v2.15.0), repeats, pas, TimeTimeLoc, TimeLoc arr, rsp, fsp; any other cmd or
13117 TimeLoc (dep) OK [must have departure & arrival before another split]
13118 Sns -> No starts, finishes except Frh & Fjo (as of v2.15.0), repeats, pas, TimeTimeLoc or TimeLoc arr; any other cmd or TimeLoc (dep) OK
13119 Sns-sh -> No starts, finishes, repeats, pas or TimeTimeLoc; any other cmd or TimeLoc (dep) OK (must have a TimeLoc departure somewhere in
13120 sequence to
13121 set location, else fails)
13122 Sns-fsh -> No starts, finishes, repeats, pas or TimeTimeLoc; any other cmd or TimeLoc (dep) OK (must have a TimeLoc departure somewhere in
13123 sequence to
13124 set location, else fails)
13125 Fns -> R only [must be preceded by a TimeLoc arrival at the finish location, not necessarily immediately]
13126 F-nshs -> Nothing (no repeats permitted)
13127 Fjo -> R only
13128 Frh -> R only
13129 Fer -> R only
13130 Frh-sh -> R only
13131 Fns-sh -> R only
13132 jbo -> No starts, repeats, pas, Fer or TimeTimeLoc; TimeLoc (dep), others OK [must be preceded by an event whose location is set]
13133 fsp -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK [must be preceded by an event whose location is set]
13134 rsp -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK [must be preceded by an event whose location is set]
13135 cdt -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK [must be preceded by an event whose location is set]
13136 dsc -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK [must be preceded by an event whose location is set]
13137 TimeLoc (arr) -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK
13138 TimeLoc (dep) -> Fer, TimeLoc (arr), or TimeTimeLoc, (new) pas OK, no others
13139 TimeTimeLoc -> Fer, TimeLoc (arr), or TimeTimeLoc, (new) pas OK, no others
13140 (new) pas -> Fer, TimeLoc (arr), or TimeTimeLoc, (new) pas OK, no others
13141 Repeat -> Nothing
13142 
13143 There must be a TimeLoc arrival (or a Sns start at location) in a sequence so successive cmd locations can be set
13144 Check all Snt's & set Locations if located (located = zero start speed, either element at a location (but if rear element
13145 is a continuation then treated as unlocated), and location listed in the next TimeLoc entry, though needn't be immediately after)
13146 If Snt entry at a location specified in a following TimeLoc entry but start speed > 0 give error message
13147 Check all times increase or stay same through ActionVector
13148 Cycle through all entries in vector setting arr & dep times based on above list
13149 Add locations to all relevant cmd entries based on earlier arrival location (or earlier reference for Sfs & Sns)
13150 Check locations match the arr & dep TimeLoc entries
13151 Check same location doesn't appear twice before a cdt except for separate arr & dep TimeLocs
13152 Make above valid succession checks
13153 Check all splits have matching Sfs headcodes (both ways), add locations to Sfs's & check times same [Sfs loc derived from preceding fsp/rsp
13154 loc]
13155 Check all new service headcodes (Sns) have matching headcodes (both ways), add locations to Sns's & check times same [Sns loc derived from
13156 preceding Fns loc]
13157 Check a split to 'x' doesn't again split to 'x' (anywhere, not just for one train, since headcodes can be duplicated)
13158 Check each Fns has matching Sns headcodes (both ways), add locations to Fns's & check times same
13159 Check all joins have matching headcodes (both ways), locations & times & don't occur in same sequence
13160 Check each joined by train not joined by same train again (anywhere, not just for one train, since headcodes can be duplicated)
13161 Set train info for Sfs & Sns entries
13162 Check each repeat entry exactly matches any included joins or splits (user has to enter it to show that really wants it)
13163 Check at least one platform long enough for a split (only need 2 lengths) & disallow if not, need length of 2 & 1 extra
13164 element at each end, or length of 3 & 1 extra element at either end
13165 Check all TimeLocs have either Arr or Dep times set and EventTime == -1
13166 Check all Cmds have EventTime set & Arr & Dep times = -1
13167 Check all locations except unlocated Snts, Fers and Repeats have a LocationName
13168 
13169 Give messages in function if errors detected and clear the vector. Return false for failure.
13170 */
13171 
13172 /* Earlier checks:-
13173 Checks carried out with error messages in this function:-
13174 At least one comma in the line (it's based on a csv file);
13175 No entries following train information;
13176 At least one comma in remainder after train information (i.e at least a start and a finish entry);
13177 SplitEntry returns false in an intermediate entry - message repeats the entry for information;
13178 First entry not a start entry;
13179 Train information incomplete before a start entry;
13180 Entry follows a finish entry but doesn't begin with 'R';
13181 SplitEntry returns false in a finish entry - message repeats the entry for information;
13182 Last action entry isn't a finish entry.
13183 
13184 Function returns false with no message if:-
13185 Timetable start time invalid (no message is given for an invalid time as the line is assumed to be an irrelevant line; if no start
13186 time is found at all then an error message is given in the calling function);
13187 SplitTrainInfo returns false (message given in called function);
13188 SplitRepeat returns false (message given in called function).
13189 
13190 Double crosslink (shuttle) table: [OtherHeadCode, NonRepeatingShuttleLinkHeadCode, LinkedTrainEntryPtr, NonRepeatingShuttleLinkEntryPtr] <-- these for
13191 easier searching for this table
13192 
13193 Command Format OtherHead NonRepeating- LinkedTrain- NonRepeating- Decsription
13194  Code ShuttleLink- EntryPtr ShuttleLink
13195  HeadCode EntryPtr
13196 
13197 Snt-sh SNTShuttle Y (rtn shuttle) N Y (rtn sh) N Simple shuttle - no feeder service
13198 Frh-sh TimeCmdHeadCode Y (outwd shuttle) N Y (outwd sh) N Simple shuttle - no finishing service
13199 F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
13200 Sns-sh SNSShuttle Y (rtn shuttle) Y (feeder) Y (fdr-shld be rtn)N (shld be fdr) Luckily NonRep link not needed
13201 Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
13202 Fns-sh FSHNewService Y (outwd shuttle) Y (finishing) Y (outwd sh) Y (finish) Shuttle link to finishing service
13203 
13204 Note: Any shuttle start can have any finish - feeder and finish, neither, feeder but no finish & vice versa.
13205 
13206 */
13207 {
13208  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SecondPassActions,");
13209  if(TrainDataVector.empty())
13210  {
13211  SecondPassMessage(GiveMessages, "Error in timetable - there appear to be no train services in the timetable, it must contain at least one");
13212  TrainDataVector.clear();
13213  Utilities->CallLogPop(1832);
13214  return(false);
13215  }
13216 /* new preliminary checks for v0.2b without changing anything, carry each out separately:-
13217  1) must have at least one actionvector entry
13218  2) if first actionvector entry not SignallerControl then must have at least one more actionvector entry
13219  3) if first actionvector entry is SignallerControl then must have no more actionvector entries except a repeat
13220  4) first entry must be a start;
13221  4a) if first entry is Snt and second is a finish then it can't be Fns-sh or Frh-sh
13222  4b) if first entry is Sns or Sfs and second is a finish then it must be either Frh or Fjo
13223  4c) if first entry is Snt-sh, Sns-sh or Sns-fsh second can't be a finish
13224  5) a start must be the first entry;
13225  6) a repeat entry must be the last;
13226  7) for other than SignallerControl the last entry must be repeat or finish; if last entry is a repeat the last but one must be a finish;
13227  8) a finish entry must be the last or last but one, and if last but one the last must be a repeat
13228  Other successor errors will be caught later as all 'throws' changed to messages prior to the bulk of the sucessor checks
13229 */
13230 
13231  TwoLocationList.clear(); //empty the list to begin with, added at v2.9.1
13232  TwoLocationFlag = false; //added at v2.9.1
13233  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (1)
13234  {
13235  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13236  if(TrainDataVector.at(x).ActionVector.empty())
13237  {
13238  SecondPassMessage(GiveMessages, "Error in timetable - the following service has no listed events, there must be at least one: " + TDEntry.HeadCode);
13239  TrainDataVector.clear();
13240  Utilities->CallLogPop(1833);
13241  return(false);
13242  }
13243  }
13244  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (2)
13245  {
13246  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13247  TActionVectorEntry AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
13248  if(!(AVEntry0.SignallerControl))
13249  {
13250  if(TrainDataVector.at(x).ActionVector.size() == 1)
13251  {
13252  SecondPassMessage(GiveMessages, "Error in timetable - service must have a start event and at least one other for: " + TDEntry.HeadCode);
13253  TrainDataVector.clear();
13254  Utilities->CallLogPop(1822);
13255  return(false);
13256  }
13257  }
13258  }
13259  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (3)
13260  {
13261  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13262  TActionVectorEntry AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
13263  if(AVEntry0.SignallerControl)
13264  {
13265  if(TrainDataVector.at(x).ActionVector.size() > 2)
13266  {
13267  SecondPassMessage(GiveMessages,
13268  "Error in timetable - a signaller controlled service can have no more than one item (a repeat) after the start event, see: " +
13269  TDEntry.HeadCode);
13270  TrainDataVector.clear();
13271  Utilities->CallLogPop(1837);
13272  return(false);
13273  }
13274  if(TrainDataVector.at(x).ActionVector.size() > 1)
13275  {
13276  TActionVectorEntry AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
13277  if(AVEntry1.FormatType != Repeat)
13278  {
13279  SecondPassMessage(GiveMessages,
13280  "Error in timetable - a signaller controlled service cannot have any other than a repeat after the start event, see: " + TDEntry.HeadCode);
13281  TrainDataVector.clear();
13282  Utilities->CallLogPop(1838);
13283  return(false);
13284  }
13285  }
13286  }
13287  }
13288  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (4)
13289  {
13290  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13291  TActionVectorEntry AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
13292  if(AVEntry0.SequenceType != StartSequence)
13293  {
13294  SecondPassMessage(GiveMessages, "Error in timetable - the first event must be a start for: " + TDEntry.HeadCode);
13295  TrainDataVector.clear();
13296  Utilities->CallLogPop(1824);
13297  return(false);
13298  }
13299  if((AVEntry0.Command == "Snt") && !AVEntry0.SignallerControl) // (4a) sig control condition added so there is a second AVEntry
13300  // 4a added at v2.0.0. This is only a rough check, Fer only valid for an unlocated Snt
13301  // and others for a located Snt, but those checks done later
13302  {
13303  TActionVectorEntry AVEntry1 = TrainDataVector.at(x).ActionVector.at(1); // must be a second entry if first not signallercontrol
13304  if((AVEntry1.SequenceType == FinishSequence) && ((AVEntry1.Command == "Fns-sh") || (AVEntry1.Command == "Frh-sh")))
13305  {
13306  SecondPassMessage(GiveMessages, "Error in timetable - finish events Fns-sh and Frh-sh not permitted immediately after an Snt entry for: " +
13307  TDEntry.HeadCode); //these are the only AtLoc finishes not allowed
13308  TrainDataVector.clear();
13309  Utilities->CallLogPop(2046);
13310  return(false);
13311  }
13312  }
13313  if((AVEntry0.Command == "Sns") || (AVEntry0.Command == "Sfs")) // (4b)
13314  // 4b added at v2.15.0
13315  {
13316  TActionVectorEntry AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
13317  if((AVEntry1.SequenceType == FinishSequence) && (AVEntry1.Command != "Frh") && (AVEntry1.Command != "Fjo"))
13318  {
13319  SecondPassMessage(GiveMessages, "Error in timetable - only 'Frh' or 'Fjo' finish events are permitted immediately after "
13320  "an 'Sns' or 'Sfs' event for: " + TDEntry.HeadCode + ". The program is unable to determine the "
13321  "location of any other type of finish.");
13322  TrainDataVector.clear();
13323  Utilities->CallLogPop(2580);
13324  return(false);
13325  }
13326  }
13327  if((AVEntry0.Command == "Snt-sh") || (AVEntry0.Command == "Sns-sh") || (AVEntry0.Command == "Sns-fsh")) // (4c)
13328  // 4c added at v2.15.0
13329  {
13330  TActionVectorEntry AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
13331  if(AVEntry1.SequenceType == FinishSequence)
13332  {
13333  SecondPassMessage(GiveMessages, "Error in timetable - a finish event can't immediately follow an 'Snt-sh', 'Sns-sh' or 'Sns-fsh' "
13334  "event for: " + TDEntry.HeadCode);
13335  TrainDataVector.clear();
13336  Utilities->CallLogPop(2616);
13337  return(false);
13338  }
13339  }
13340  }
13341  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (5)
13342  {
13343  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13344  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13345  {
13346  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13347  if((AVEntry.SequenceType == StartSequence) && (y != 0))
13348  {
13349  SecondPassMessage(GiveMessages, "Error in timetable - a start event is present that is not the first event for: " + TDEntry.HeadCode);
13350  TrainDataVector.clear();
13351  Utilities->CallLogPop(1825);
13352  return(false);
13353  }
13354  }
13355  }
13356  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (6)
13357  {
13358  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13359  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13360  {
13361  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13362  if((AVEntry.FormatType == Repeat) && (y != (TrainDataVector.at(x).ActionVector.size() - 1)))
13363  {
13364  SecondPassMessage(GiveMessages, "Error in timetable - a repeat is present that is not the last item for: " + TDEntry.HeadCode);
13365  TrainDataVector.clear();
13366  Utilities->CallLogPop(1826);
13367  return(false);
13368  }
13369  }
13370  }
13371  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (7)
13372  {
13373  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13374  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13375  {
13376  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13377  if((y == 0) && AVEntry.SignallerControl)
13378  {
13379  break;
13380  }
13381  if(y == (TrainDataVector.at(x).ActionVector.size() - 1))
13382  {
13383  if((AVEntry.FormatType != Repeat) && (AVEntry.SequenceType != FinishSequence))
13384  {
13385  SecondPassMessage(GiveMessages, "Error in timetable - the last item must be either a finish event or a repeat for: " + TDEntry.HeadCode);
13386  TrainDataVector.clear();
13387  Utilities->CallLogPop(1827);
13388  return(false);
13389  }
13390  if(AVEntry.FormatType == Repeat)
13391  {
13392  const TActionVectorEntry &LastButOneAVEntry = TrainDataVector.at(x).ActionVector.at(y - 1);
13393  if(LastButOneAVEntry.SequenceType != FinishSequence)
13394  {
13395  SecondPassMessage(GiveMessages, "Error in timetable - the event immediately before the repeat must be a finish for: " + TDEntry.HeadCode);
13396  TrainDataVector.clear();
13397  Utilities->CallLogPop(1828);
13398  return(false);
13399  }
13400  }
13401  }
13402  }
13403  }
13404  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (8)
13405  {
13406  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13407  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13408  {
13409  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13410  if(AVEntry.SequenceType == FinishSequence)
13411  {
13412  if((y != (TrainDataVector.at(x).ActionVector.size() - 1)) && (y != (TrainDataVector.at(x).ActionVector.size() - 2)))
13413  {
13414  SecondPassMessage(GiveMessages, "Error in timetable - a finish event must be either the last or last but one for: " + TDEntry.HeadCode);
13415  TrainDataVector.clear();
13416  Utilities->CallLogPop(1829);
13417  return(false);
13418  }
13419  if(y == (TrainDataVector.at(x).ActionVector.size() - 2))
13420  {
13421  if(TrainDataVector.at(x).ActionVector.at(y + 1).FormatType != Repeat)
13422  {
13423  SecondPassMessage(GiveMessages, "Error in timetable - the only event that can follow a finish event is a repeat for: " + TDEntry.HeadCode);
13424  TrainDataVector.clear();
13425  Utilities->CallLogPop(1830);
13426  return(false);
13427  }
13428  }
13429  }
13430  }
13431  }
13432 
13433  // end of new preliminary checks
13434 
13435  // check start event successor validity
13436  // For Snt & Snt-sh set location if stopped, don't set any times yet
13437  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13438  {
13439  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13440  TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
13441  // use reference so can change internals where necessary
13442  if((AVEntry0.Command == "Snt") || (AVEntry0.Command == "Snt-sh"))
13443  {
13444  AnsiString LocationName = "";
13445  if(IsSNTEntryLocated(0, TDEntry, LocationName))
13446  // it is at a location
13447  {
13448  AVEntry0.LocationName = LocationName; //located Snt location name set
13449  AVEntry0.LocationType = AtLocation;
13450  // check successor validity for located Snt that isn't a SignallerControl entry
13451  if(!AVEntry0.SignallerControl)
13452  {
13453  const TActionVectorEntry &AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
13454  // at least 2 entries present checked in integrity check so (1) valid
13455  if(!AtLocSuccessor(AVEntry1))
13456  {
13457  // Frh following Snt-sh will return false in location check, so no need to check here
13458  SecondPassMessage(GiveMessages, "Error in timetable - stopped 'Snt' or 'Snt-sh' followed by an illegal event for: " +
13459  TDEntry.HeadCode + ". The event isn't valid for a stationary train.");
13460  TrainDataVector.clear();
13461  Utilities->CallLogPop(523);
13462  return(false);
13463  }
13464  }
13465  }
13466  else // check not Snt-sh & carry out successor validity checks for unlocated Snt that isn't a SignallerControl entry
13467  {
13468  if(AVEntry0.Command == "Snt-sh")
13469  {
13470  SecondPassMessage(GiveMessages, "Error in timetable - 'Snt-sh' event not at stop location for: " + TDEntry.HeadCode);
13471  TrainDataVector.clear();
13472  Utilities->CallLogPop(1042);
13473  return(false);
13474  }
13475  AVEntry0.LocationType = EnRoute;
13476  if(!AVEntry0.SignallerControl)
13477  {
13478  const TActionVectorEntry &AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
13479  // at least 2 entries checked in integrity check so (1) valid
13480  if(!MovingSuccessor(AVEntry1))
13481  {
13482  SecondPassMessage(GiveMessages, "Error in timetable - unlocated 'Snt' not followed by 'Fer', 'pas' or an arrival for: " +
13483  TDEntry.HeadCode);
13484  TrainDataVector.clear();
13485  Utilities->CallLogPop(790);
13486  return(false);
13487  }
13488  }
13489  }
13490  }
13491  // check other start successors, all AtLoc
13492  else if(AVEntry0.SequenceType == StartSequence)
13493  {
13494  const TActionVectorEntry &AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
13495  // at least 2 entries present checked in integrity check so (1) valid
13496  if(!AtLocSuccessor(AVEntry1))
13497  {
13498  SecondPassMessage(GiveMessages, "Error in timetable - 'Sfs', 'Sns', 'Sns-sh', 'Snt-fsh' or 'Sns-fsh' followed by an illegal event for: " +
13499  TDEntry.HeadCode + ". The event isn't valid for a stationary train.");
13500  TrainDataVector.clear();
13501  Utilities->CallLogPop(793);
13502  return(false);
13503  }
13504  }
13505  }
13506 
13507 
13508  // set Sns-sh & Sns-fsh locations same as following TimeLoc departure entry location, if no departure before end of sequence give error message
13509  for(unsigned int x = 0; x < TrainDataVector.size(); x++) //at v2.15.0 set Sfs & Sns locations from corresponding fsp/rsp & Fns entries, shuttles ok as they are
13510  {
13511  bool FoundFlag = false;
13512  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13513  TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
13514  // use reference so can change internals
13515  if((AVEntry0.Command == "Sns-sh") || (AVEntry0.Command == "Sns-fsh"))
13516  {
13517  for(unsigned int y = 1; y < TrainDataVector.at(x).ActionVector.size(); y++)
13518  {
13519  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y);
13520  if(AVEntry2.FormatType == TimeLoc)
13521  {
13522  FoundFlag = true;
13523  AVEntry0.LocationName = AVEntry2.LocationName; //Sns-sh & Sns-fsh location names set
13524  break;
13525  }
13526  }
13527  if(!FoundFlag)
13528  {
13529  SecondPassMessage(GiveMessages, "Error in timetable - no location departure following an 'Sns-sh' or 'Sns-fsh' event for: " + TDEntry.HeadCode);
13530  TrainDataVector.clear();
13531  Utilities->CallLogPop(851);
13532  return(false);
13533  }
13534  }
13535  }
13536 
13537 //carry out preliminary check on service ref linkages without setting any data - added at v2.15.0 as can be location errors if linked trains not present
13538 //first check for duplicates then linkages (also checked later but leave that in)
13539  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13540  {
13541  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13542  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13543  {
13544  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13545  if(AVEntry.OtherHeadCode != "")
13546  {
13547  if(!CheckForDuplicateCrossReferences(2, TDEntry.HeadCode, AVEntry.OtherHeadCode, GiveMessages))
13548  {
13549  Utilities->CallLogPop(2610);
13550  return(false); // error message given in called function
13551  }
13552  }
13553  if(AVEntry.NonRepeatingShuttleLinkHeadCode != "")
13554  {
13555  if(!CheckForDuplicateCrossReferences(3, TDEntry.HeadCode, AVEntry.NonRepeatingShuttleLinkHeadCode, GiveMessages))
13556  {
13557  Utilities->CallLogPop(2611);
13558  return(false); // error message given in called function
13559  }
13560  }
13561  }
13562  }
13563 //cross reference check
13564  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13565  {
13566  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13567  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13568  {
13569  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13570  if((AVEntry.Command != "Sns-sh") && (AVEntry.Command != "Snt-sh") && (AVEntry.Command != "Fns-sh") && (AVEntry.Command != "Frh-sh"))
13571  {
13572  if(AVEntry.OtherHeadCode != "")
13573  {
13574  if(!CheckCrossReferencesAndSetData(2, TDEntry.HeadCode, AVEntry.OtherHeadCode, false, false, GiveMessages))
13575  // false = non-shuttle
13576  {
13577  Utilities->CallLogPop(2612);
13578  return(false); // error message given in called function
13579  }
13580  }
13581  }
13582  }
13583  }
13584 
13585 // now repeat the check just for the shuttles
13586  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13587  {
13588  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13589  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13590  {
13591  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13592  if((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "Frh-sh"))
13593  {
13594  if(AVEntry.OtherHeadCode != "")
13595  {
13596  if(!CheckCrossReferencesAndSetData(3, TDEntry.HeadCode, AVEntry.OtherHeadCode, true, false, GiveMessages))
13597  // true = shuttle
13598  {
13599  Utilities->CallLogPop(2613);
13600  return(false); // error message given in called function
13601  }
13602  }
13603  }
13604  }
13605  }
13606 
13607 // check for proper non-repeating link cross references and that they have no repeats & that times are consistent
13608  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13609  {
13610  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13611  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13612  {
13613  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13614  if(AVEntry.NonRepeatingShuttleLinkHeadCode != "")
13615  {
13616  if(!CheckNonRepeatingShuttleLinksAndSetData(1, TDEntry.HeadCode, AVEntry.NonRepeatingShuttleLinkHeadCode, false, GiveMessages))
13617  {
13618  Utilities->CallLogPop(2614);
13619  return(false); // error message given in called function
13620  }
13621  }
13622  }
13623  }
13624 
13625 //at v2.15.0 we want to set Sns, Sfs location names, but to set Sns & Sfs first need the linked Fns & fsp/rsp to have locations set as they aren't yet,
13626 //and before v2.15.0 they were set from the corresponding Sns & Sfs locations, which in turn were set from later TimeLoc departures. At v2.15.0 it
13627 //is required to have these commands followed by Frh & Fjo, so this is why we need the linked Fns & fsp/rsp to have locations set first. Now all Fns
13628 //will have a TimeLoc before, so that can provide its location, but fsp/rsp? Must they have a TimeLoc before? No, and can't rely on starting Sfs
13629 //having the location set yet. So, new restriction, insist on an rsp/fsp having a TimeLoc before it or a located Snt, and use one of those to set the
13630 //location for the rsp/fsp and hence the linked Sfs.
13631 
13632 //NB can't allow an Sfs to be followed by another split or won't find a name, test with many existing tts then add an error to find it
13633 //Fns must be preceded by an arrival
13634 
13635 //set name for Fns from earlier location name or fail if can't find
13636  bool LocFoundFlag, FnsFoundFlag;
13637  TActionVectorEntry *AVEntryFns;
13638  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13639  {
13640  LocFoundFlag = false;
13641  FnsFoundFlag = false;
13642  for(int y = TrainDataVector.at(x).ActionVector.size() - 1; y >= 0; y--) //search backwards
13643  {
13644  if(TrainDataVector.at(x).ActionVector.at(y).Command == "Fns")
13645  {
13646  AVEntryFns = &TrainDataVector.at(x).ActionVector.at(y);
13647  FnsFoundFlag = true;
13648  continue;
13649  }
13650  if(!FnsFoundFlag)
13651  {
13652  continue;
13653  }
13654  if(TrainDataVector.at(x).ActionVector.at(y).LocationName != "")
13655  {
13656  LocFoundFlag = true;
13657  AVEntryFns->LocationName = TrainDataVector.at(x).ActionVector.at(y).LocationName;
13658 // double EVT = double(AVEntryFns->EventTime); //test
13659  break; //name found
13660  }
13661  if(TrainDataVector.at(x).ActionVector.at(y).LocationType == AtLocation) //not named yet
13662  {
13663  continue;
13664  }
13665  else
13666  {
13667  SecondPassMessage(GiveMessages, "Error in timetable - the program can't determine the location of an 'Fns' finish, it must be preceded "
13668  "by an event at the same location that has an identified location name, normally an arrival, see "
13669  + TrainDataVector.at(x).ServiceReference);
13670  TrainDataVector.clear();
13671  Utilities->CallLogPop(2596);
13672  return(false);
13673  }
13674  }
13675  if(FnsFoundFlag && !LocFoundFlag)
13676  {
13677  SecondPassMessage(GiveMessages, "Error in timetable - the program can't determine the location of an 'Fns' finish, it must be preceded "
13678  "by an event at the same location that has an identified location name, normally an arrival, see "
13679  + TrainDataVector.at(x).ServiceReference);
13680  TrainDataVector.clear();
13681  Utilities->CallLogPop(2597);
13682  return(false);
13683  }
13684  }
13685 
13686 //now set all names for Sns entries from the above, new at v2.15.0
13687  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13688  {
13689  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13690  TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
13691  // use reference so can change internals
13692  if(AVEntry0.Command == "Sns")
13693  {
13694  //new at v2.15.0. Only set location if have a forward and backward linkage at same time (these all set when SecondPassActions called).
13695  //This isn't rigorous as may have more than one, but if do then will be caught below in CheckCrossReferencesAndSetData
13696  //If fail to find location just ignore as will be caught later in CheckCrossReferencesAndSetData
13697  //note that at this stage the OtherHeadCode values are service refs, as haven't yet been changed back to headcodes until
13698  //StripExcessFromHeadCode called at end of this function
13699  //need to be the same: forward & reverse service refs, event times, commands correspond
13700 
13701  //successor checks first: /no starts, /finishes except Frh & Fjo (as of v2.15.0), /repeats, /pas, /TimeTimeLoc or /TimeLoc arr; any other cmd or TimeLoc (dep) OK
13702  if(TDEntry.ActionVector.size() < 2)
13703  {
13704  SecondPassMessage(GiveMessages, "Error in timetable - insufficient actions follwing an 'Sns' event for: " + TDEntry.HeadCode);
13705  TrainDataVector.clear();
13706  Utilities->CallLogPop(2598);
13707  return(false);
13708  }
13709  TActionVectorEntry AVEntry1 = TDEntry.ActionVector.at(1);
13710  if(!AtLocSuccessor(AVEntry1))
13711  {
13712  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sns' event is followed by an illegal event for: " + TDEntry.HeadCode +
13713  ". The event isn't valid for a stationary train.");
13714  TrainDataVector.clear();
13715  Utilities->CallLogPop(2599);
13716  return(false);
13717  }
13718  if((AVEntry1.SequenceType == StartSequence) || ((AVEntry1.SequenceType == FinishSequence) && (AVEntry1.Command != "Frh") && (AVEntry1.Command != "Fjo")) ||
13719  (AVEntry1.FormatType == Repeat))
13720  {
13721  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sns' event is followed by an illegal event for: " + TDEntry.HeadCode);
13722  TrainDataVector.clear();
13723  Utilities->CallLogPop(2600);
13724  return(false);
13725  }
13726 
13727  //now set the location and location type
13728  TDateTime SnsEventTime = AVEntry0.EventTime;
13729 // double EVT = double(SnsEventTime); //test
13730  AnsiString SnsServiceRef = TDEntry.ServiceReference;
13731  bool BreakFlag = false;
13732  for(unsigned int y = 0; y < TrainDataVector.size(); y++)
13733  {
13734  for(unsigned int z = 0; z < TrainDataVector.at(y).ActionVector.size(); z++)
13735  {
13736  if((TrainDataVector.at(y).ActionVector.at(z).Command == "Fns") && (SnsEventTime == TrainDataVector.at(y).ActionVector.at(z).EventTime) &&
13737  (TrainDataVector.at(y).HeadCode == AVEntry0.OtherHeadCode))
13738  { //forward linkage found
13739  if(TrainDataVector.at(y).ActionVector.at(z).OtherHeadCode == SnsServiceRef) //OtherHeadCode values are service refs, see above
13740  { //reverse linkage found
13741  AVEntry0.LocationName = TrainDataVector.at(y).ActionVector.at(z).LocationName;
13742  AVEntry0.LocationType = AtLocation;
13743  BreakFlag = true;
13744  break;
13745  }
13746  }
13747  }
13748  if(BreakFlag)
13749  {
13750  break;
13751  }
13752  }
13753  //test for any unnamed AtLoc entries at end of name setting
13754  }
13755  }
13756 
13757 //trap errors where rsp/fsp follows an Sfs without a TimeLoc arrival before (or unlikely to be able to set fsp/rsp/Sfs location because Sfs locs set from linked fsp/rsp events)
13758  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13759  {
13760  const TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
13761  if(AVEntry0.Command == "Sfs")
13762  {
13763  for(unsigned int y = 1; y < TrainDataVector.at(x).ActionVector.size(); y++)
13764  {
13765  if(TrainDataVector.at(x).ActionVector.at(y).LocationName != "") //must be a timeloc as only they have loc set and are AtLoc (non-AtLoc trapped above)
13766  {
13767  break;
13768  }
13769  else if((TrainDataVector.at(x).ActionVector.at(y).Command == "fsp") || (TrainDataVector.at(x).ActionVector.at(y).Command == "rsp"))
13770  {
13771  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sfs' event must be followed by a departure and arrival before another split, see " + TrainDataVector.at(x).ServiceReference);
13772  TrainDataVector.clear();
13773  Utilities->CallLogPop(2586);
13774  return(false);
13775  }
13776  }
13777  }
13778  }
13779 
13780 //now name fsp/rsp actions
13781  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13782  {
13783  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13784  {
13785  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13786  if(AVEntry.LocationName != "")
13787  {
13788  for(unsigned int z = y + 1; z < TrainDataVector.at(x).ActionVector.size(); z++)
13789  {
13790  TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(z);
13791  // use reference so can change internals where necessary
13792  if((AVEntry2.Command == "fsp") || (AVEntry2.Command == "rsp"))
13793  {
13794  AVEntry2.LocationName = AVEntry.LocationName;
13795  } //test for any unnamed AtLoc entries at end of name setting
13796  else if(AVEntry2.LocationType != AtLocation)
13797  {
13798  break;
13799  }
13800  }
13801  }
13802  }
13803  }
13804 
13805 //check that all named or give error message
13806  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13807  {
13808  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13809  {
13810  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13811  if((AVEntry.Command == "fsp") || (AVEntry.Command == "rsp"))
13812  {
13813  if(AVEntry.LocationName == "")
13814  {
13815  SecondPassMessage(GiveMessages, "Error in timetable - an 'fsp' or 'rsp' event must be preceded by an event at the same location that has an identified location name, normally an arrival, see " + TrainDataVector.at(x).ServiceReference);
13816  TrainDataVector.clear();
13817  Utilities->CallLogPop(2617);
13818  return(false);
13819  }
13820  }
13821  }
13822  }
13823 
13824 //now set all Sfs entries from the above
13825  for(unsigned int x = 0; x < TrainDataVector.size(); x++) //at v2.15.0 set Sfs & Sns locations from corresponding fsp/rsp & Fns entries, shuttles ok as they are
13826  {
13827  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13828  TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
13829  // use reference so can change internals
13830  if(AVEntry0.Command == "Sfs")
13831  {
13832  //new at v2.15.0. Only set location if have a forward and backward linkage at same time (these all set when SecondPassActions called). This isn't
13833  //rigorous as may have more than one, but if do then will be caught below in CheckCrossReferencesAndSetData
13834  //If fail to find location just ignore as will be caught later in CheckCrossReferencesAndSetData
13835  //note that at this stage the OtherHeadCode values are service refs, as haven't yet been changed back to headcodes until StripExcessFromHeadCode called
13836  //at end of this function
13837  //need to be the same: forward & reverse service refs, event times, commands correspond
13838 
13839  //successor checks first: /no starts, /finishes except Frh & Fjo (as of v2.15.0), /repeats, /pas, /TimeTimeLoc or /TimeLoc arr; any other cmd or TimeLoc (dep) OK
13840  if(TDEntry.ActionVector.size() < 2)
13841  {
13842  SecondPassMessage(GiveMessages, "Error in timetable - insufficient actions follwing an 'Sfs' event for: " + TDEntry.HeadCode);
13843  TrainDataVector.clear();
13844  Utilities->CallLogPop(2587);
13845  return(false);
13846  }
13847  TActionVectorEntry AVEntry1 = TDEntry.ActionVector.at(1);
13848  if(!AtLocSuccessor(AVEntry1))
13849  {
13850  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sfs' event is followed by an illegal event for: " + TDEntry.HeadCode +
13851  ". The event isn't valid for a stationary train.");
13852  TrainDataVector.clear();
13853  Utilities->CallLogPop(2588);
13854  return(false);
13855  }
13856  if((AVEntry1.SequenceType == StartSequence) || ((AVEntry1.SequenceType == FinishSequence) && (AVEntry1.Command != "Frh") && (AVEntry1.Command != "Fjo")) ||
13857  (AVEntry1.FormatType == Repeat))
13858  {
13859  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sfs' event is followed by an illegal event for: " + TDEntry.HeadCode);
13860  TrainDataVector.clear();
13861  Utilities->CallLogPop(2589);
13862  return(false);
13863  }
13864 
13865  //now set the location and location type
13866  TDateTime SfsEventTime = AVEntry0.EventTime;
13867  AnsiString SfsServiceRef = TDEntry.ServiceReference;
13868  bool BreakFlag = false;
13869  for(unsigned int y = 0; y < TrainDataVector.size(); y++)
13870  {
13871  for(unsigned int z = 0; z < TrainDataVector.at(y).ActionVector.size(); z++)
13872  {
13873  if(((TrainDataVector.at(y).ActionVector.at(z).Command == "fsp") || (TrainDataVector.at(y).ActionVector.at(z).Command == "rsp")) &&
13874  (SfsEventTime == TrainDataVector.at(y).ActionVector.at(z).EventTime) && (TrainDataVector.at(y).HeadCode == AVEntry0.OtherHeadCode))
13875  { //forward linkage found
13876  if(TrainDataVector.at(y).ActionVector.at(z).OtherHeadCode == SfsServiceRef) //OtherHeadCode values are service refs, see above
13877  { //reverse linkage found
13878  AVEntry0.LocationName = TrainDataVector.at(y).ActionVector.at(z).LocationName;
13879  AVEntry0.LocationType = AtLocation;
13880  BreakFlag = true;
13881  break;
13882  }
13883  }
13884  }
13885  if(BreakFlag)
13886  {
13887  break;
13888  }
13889  } //test for any unnamed AtLoc entries at end of name setting
13890  }
13891  }
13892 
13893  // set all cmd locations based on earlier location name in TimeLoc arrival or Sfs/Sns/Sns-sh/Sns-fsh/located Snt/Snt-sh locations
13894  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13895  {
13896  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13897  {
13898  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13899  if(AVEntry.LocationName != "")
13900  {
13901  for(unsigned int z = y + 1; z < TrainDataVector.at(x).ActionVector.size(); z++)
13902  {
13903  TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(z);
13904  // use reference so can change internals where necessary
13905  if((AVEntry2.Command != "") && (AVEntry2.LocationType == AtLocation))
13906  {
13907  AVEntry2.LocationName = AVEntry.LocationName;
13908  } //test for any unnamed AtLoc entries at end of name setting
13909  else
13910  {
13911  break;
13912  }
13913  }
13914  }
13915  }
13916  }
13917  // all location names should now be set
13918 
13919 //now test for any unnamed AtLoc entries where Command != "" and give message if find any - shouldn't find any if above checks comprehensive
13920  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13921  {
13922  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13923  {
13924  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13925  if((AVEntry.LocationType == AtLocation) && (AVEntry.LocationName == "") && (AVEntry.Command != ""))
13926  {
13927  if((AVEntry.Command == "jbo") || (AVEntry.Command == "fsp") || (AVEntry.Command == "rsp") || (AVEntry.Command == "cdt") || (AVEntry.Command == "dsc"))
13928  {
13929  SecondPassMessage(GiveMessages, "Error in timetable - '" + AVEntry.Command + "' must be preceded by an event at the same location that has an identified location name, normally an arrival, see " + TrainDataVector.at(x).ServiceReference);
13930  TrainDataVector.clear();
13931  Utilities->CallLogPop(2619);
13932  return(false);
13933  }
13934  else if((AVEntry.Command == "Sns") || (AVEntry.Command == "Sfs"))
13935  {
13936  SecondPassMessage(GiveMessages, "Error in timetable - the location of the '" + AVEntry.Command + "' event in service " + TrainDataVector.at(x).ServiceReference + " can't be identified. " +
13937  "Please make sure that the finish event of the service that links to this event is preceded by an "
13938  "event at the same location that has an identified location name, normally an arrival.");
13939  TrainDataVector.clear();
13940  Utilities->CallLogPop(2620);
13941  return(false);
13942  }
13943  else if(AVEntry.Command == "Snt-sh")
13944  {
13945  SecondPassMessage(GiveMessages, "Error in timetable - the location of the '" + AVEntry.Command + "' event in service " + TrainDataVector.at(x).ServiceReference + " can't be identified. " +
13946  "Please make sure that the service starts with zero speed and is at a named location.");
13947  TrainDataVector.clear();
13948  Utilities->CallLogPop(2623);
13949  return(false);
13950  }
13951  else if((AVEntry.Command == "Sns-fsh") || (AVEntry.Command == "Sns-sh"))
13952  {
13953  SecondPassMessage(GiveMessages, "Error in timetable - the location of the '" + AVEntry.Command + "' event in service " + TrainDataVector.at(x).ServiceReference + " can't be identified. " +
13954  "Please make sure that the event is followed (not necessarily immediately) by a departure.");
13955  TrainDataVector.clear();
13956  Utilities->CallLogPop(2622);
13957  return(false);
13958  }
13959  else if((AVEntry.Command == "Frh") || (AVEntry.Command == "Fns") || (AVEntry.Command == "Fjo") || (AVEntry.Command == "Frh-sh") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "F-nshs"))
13960  {
13961  SecondPassMessage(GiveMessages, "Error in timetable - '" + AVEntry.Command + "must be preceded by an event at the same location that has an identified location name, normally an arrival, see " + TrainDataVector.at(x).ServiceReference);
13962  TrainDataVector.clear();
13963  Utilities->CallLogPop(2621);
13964  return(false);
13965  }
13966  }
13967  }
13968  }
13969 
13970  // check remaining successor validity except for TimeLoc arr & dep since those times not set yet
13971  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13972  {
13973  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13974  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13975  {
13976  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13977  if((AVEntry.SequenceType == FinishSequence) && (AVEntry.Command != "F-nshs"))
13978  {
13979  if(y < (TrainDataVector.at(x).ActionVector.size() - 1))
13980  // i.e at least one more, must be a repeat
13981  {
13982  if(TrainDataVector.at(x).ActionVector.at(y + 1).FormatType != Repeat)
13983  {
13984  SecondPassMessage(GiveMessages, "Error in timetable - only a repeat can follow a finish event for: " + TDEntry.HeadCode);
13985  TrainDataVector.clear();
13986  Utilities->CallLogPop(798);
13987  return(false);
13988  }
13989  }
13990  }
13991  if(AVEntry.Command == "F-nshs")
13992  {
13993  if(y != (TrainDataVector.at(x).ActionVector.size() - 1))
13994  // i.e has to be the last
13995  {
13996  SecondPassMessage(GiveMessages, "Error in timetable - F-nshs (shuttle link) must be the last event for: " + TDEntry.HeadCode);
13997  TrainDataVector.clear();
13998  Utilities->CallLogPop(1049);
13999  return(false);
14000  }
14001  }
14002  if(AVEntry.Command == "pas")
14003  {
14004  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14005  {
14006  SecondPassMessage(GiveMessages, "Error in timetable - a 'pas' can't be the last event for: " + TDEntry.HeadCode);
14007  TrainDataVector.clear();
14008  Utilities->CallLogPop(1518);
14009  return(false);
14010  }
14011  }
14012  if(AVEntry.Command == "jbo")
14013  {
14014  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14015  {
14016  SecondPassMessage(GiveMessages, "Error in timetable - a 'jbo' can't be the last event for: " + TDEntry.HeadCode);
14017  TrainDataVector.clear();
14018  Utilities->CallLogPop(800);
14019  return(false);
14020  }
14021  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14022  if(!AtLocSuccessor(AVEntry2))
14023  {
14024  SecondPassMessage(GiveMessages, "Error in timetable - a jbo event is followed by an illegal event for: " + TDEntry.HeadCode +
14025  ". The event isn't valid for a stationary train.");
14026  TrainDataVector.clear();
14027  Utilities->CallLogPop(801);
14028  return(false);
14029  }
14030  }
14031  if((AVEntry.Command == "fsp") || (AVEntry.Command == "rsp"))
14032  {
14033  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14034  {
14035  SecondPassMessage(GiveMessages, "Error in timetable - a train split can't be the last event for: " + TDEntry.HeadCode);
14036  TrainDataVector.clear();
14037  Utilities->CallLogPop(802);
14038  return(false);
14039  }
14040  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14041  if(!AtLocSuccessor(AVEntry2))
14042  {
14043  SecondPassMessage(GiveMessages, "Error in timetable - a train split is followed by an illegal event for: " + TDEntry.HeadCode +
14044  ". The event isn't valid for a stationary train.");
14045  TrainDataVector.clear();
14046  Utilities->CallLogPop(803);
14047  return(false);
14048  }
14049  }
14050  if(AVEntry.Command == "cdt")
14051  {
14052  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14053  {
14054  SecondPassMessage(GiveMessages, "Error in timetable - a 'cdt' can't be the last event for: " + TDEntry.HeadCode);
14055  TrainDataVector.clear();
14056  Utilities->CallLogPop(804);
14057  return(false);
14058  }
14059  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14060  if(!AtLocSuccessor(AVEntry2))
14061  {
14062  SecondPassMessage(GiveMessages, "Error in timetable - a 'cdt' is followed by an illegal event for: " + TDEntry.HeadCode +
14063  ". The event isn't valid for a stationary train.");
14064  TrainDataVector.clear();
14065  Utilities->CallLogPop(805);
14066  return(false);
14067  }
14068  }
14069  if(AVEntry.Command == "dsc")
14070  {
14071  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14072  {
14073  SecondPassMessage(GiveMessages, "Error in timetable - a 'dsc' can't be the last event for: " + TDEntry.HeadCode);
14074  TrainDataVector.clear();
14075  Utilities->CallLogPop(2602);
14076  return(false);
14077  }
14078  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14079  if(!AtLocSuccessor(AVEntry2))
14080  {
14081  SecondPassMessage(GiveMessages, "Error in timetable - a 'dsc' is followed by an illegal event for: " + TDEntry.HeadCode +
14082  ". The event isn't valid for a stationary train.");
14083  TrainDataVector.clear();
14084  Utilities->CallLogPop(2603);
14085  return(false);
14086  }
14087  }
14088  if(AVEntry.FormatType == TimeTimeLoc)
14089  {
14090  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14091  {
14092  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival and departure can't be the last event for: " + TDEntry.HeadCode);
14093  TrainDataVector.clear();
14094  Utilities->CallLogPop(806);
14095  return(false);
14096  }
14097  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14098  if(!MovingSuccessor(AVEntry2))
14099  {
14100  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival and departure is followed by an illegal event for: " +
14101  TDEntry.HeadCode + ". The event isn't valid for a moving train.");
14102  TrainDataVector.clear();
14103  Utilities->CallLogPop(807);
14104  return(false);
14105  }
14106  }
14107  if(AVEntry.FormatType == PassTime)
14108  {
14109  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14110  {
14111  SecondPassMessage(GiveMessages, "Error in timetable - a pass time can't be the last event for: " + TDEntry.HeadCode);
14112  TrainDataVector.clear();
14113  Utilities->CallLogPop(1530);
14114  return(false);
14115  }
14116  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14117  if(!MovingSuccessor(AVEntry2))
14118  {
14119  SecondPassMessage(GiveMessages, "Error in timetable - a pass time is followed by an illegal event for: " + TDEntry.HeadCode +
14120  ". The event isn't valid for a moving train.");
14121  TrainDataVector.clear();
14122  Utilities->CallLogPop(1531);
14123  return(false);
14124  }
14125  }
14126  if(AVEntry.FormatType == Repeat)
14127  {
14128  if(y != (TrainDataVector.at(x).ActionVector.size() - 1))
14129  {
14130  SecondPassMessage(GiveMessages, "Error in timetable - a repeat is not the last item for: " + TDEntry.HeadCode);
14131  TrainDataVector.clear();
14132  Utilities->CallLogPop(808);
14133  return(false);
14134  }
14135  }
14136  }
14137  }
14138 
14139  // set arrival & departure times for TimeLocs & set their EventTimes to -1
14140  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14141  {
14142  bool LastEntryIsAnArrival = false;
14143  const TTrainDataEntry & TDEntry = TrainDataVector.at(x);
14144  // first deal with unlocated Snt entries - so next entry (TimeLoc or TimeTimeLoc) is an arrival, all else stopped so the next TimeLoc is a departure
14145  const TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14146  if((AVEntry0.Command == "Snt") && (AVEntry0.LocationType == EnRoute))
14147  // StartSpeed may or may not be 0, but train will move forwards (if capable of doing so), & next TimeLoc will be an arrival, whether or not after one or more TimeTimeLocs
14148  {
14149  LastEntryIsAnArrival = false;
14150  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14151  {
14152  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14153  if(AVEntry.FormatType == TimeLoc)
14154  {
14155  if((AVEntry.ArrivalTime > TDateTime(-1)) || (AVEntry.DepartureTime > TDateTime(-1)) || (AVEntry.EventTime == TDateTime(-1)))
14156  {
14157  throw Exception("Timetable error, TimeLoc times not as initially set for " + TDEntry.HeadCode);
14158  }
14159  if(LastEntryIsAnArrival)
14160  {
14161  AVEntry.DepartureTime = AVEntry.EventTime;
14162  AVEntry.EventTime = TDateTime(-1);
14163  LastEntryIsAnArrival = false;
14164  }
14165  else // last entry a departure
14166  {
14167  AVEntry.ArrivalTime = AVEntry.EventTime;
14168  AVEntry.EventTime = TDateTime(-1);
14169  LastEntryIsAnArrival = true;
14170  }
14171  }
14172  }
14173  }
14174  else // all others stopped at beginning
14175  {
14176  LastEntryIsAnArrival = true;
14177  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14178  {
14179  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14180  if(AVEntry.FormatType == TimeLoc)
14181  {
14182  if((AVEntry.ArrivalTime > TDateTime(-1)) || (AVEntry.DepartureTime > TDateTime(-1)) || (AVEntry.EventTime == TDateTime(-1)))
14183  {
14184  throw Exception("Timetable error, TimeLoc times not as initially set for " + TDEntry.HeadCode);
14185  }
14186  if(LastEntryIsAnArrival)
14187  {
14188  AVEntry.DepartureTime = AVEntry.EventTime;
14189  AVEntry.EventTime = TDateTime(-1);
14190  LastEntryIsAnArrival = false;
14191  }
14192  else // last entry a departure
14193  {
14194  AVEntry.ArrivalTime = AVEntry.EventTime;
14195  AVEntry.EventTime = TDateTime(-1);
14196  LastEntryIsAnArrival = true;
14197  }
14198  }
14199  }
14200  }
14201  }
14202  // perform remaining successor checks for TimeLocs
14203  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14204  {
14205  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14206  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14207  {
14208  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14209  if((AVEntry.FormatType == TimeLoc) && (AVEntry.ArrivalTime >= TDateTime(0))) // arrival
14210  // TimeLoc (arr) -> No starts, repeats, Fer or TimeTimeLoc; TimeLoc (dep) or any other OK
14211  {
14212  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14213  {
14214  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival can't be the last event for: " + TDEntry.HeadCode);
14215  TrainDataVector.clear();
14216  Utilities->CallLogPop(809);
14217  return(false);
14218  }
14219  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14220  if(!AtLocSuccessor(AVEntry2))
14221  {
14222  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival is followed by an illegal event for: " + TDEntry.HeadCode +
14223  ". The event isn't valid for a stationary train.");
14224  TrainDataVector.clear();
14225  Utilities->CallLogPop(810);
14226  return(false);
14227  }
14228  }
14229  if((AVEntry.FormatType == TimeLoc) && (AVEntry.DepartureTime >= TDateTime(0))) // departure
14230  // TimeLoc (dep) -> Fer, TimeLoc (arr), TimeTimeLoc, (new) pas OK, no others
14231  {
14232  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14233  {
14234  SecondPassMessage(GiveMessages, "Error in timetable - a timed departure can't be the last event for: " + TDEntry.HeadCode);
14235  TrainDataVector.clear();
14236  Utilities->CallLogPop(811);
14237  return(false);
14238  }
14239  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14240  if(!MovingSuccessor(AVEntry2))
14241  {
14242  SecondPassMessage(GiveMessages, "Error in timetable - a timed departure is followed by an illegal event for: " + TDEntry.HeadCode +
14243  ". The event isn't valid for a moving train.");
14244  TrainDataVector.clear();
14245  Utilities->CallLogPop(812);
14246  return(false);
14247  }
14248  }
14249  }
14250  }
14251 
14252  // check all TimeLocs have either Arr or Dep time set and EventTime == -1, all Cmds have EventTime set & Arr & Dep times == -1,
14253  // & repeats have no times set
14254  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14255  {
14256  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14257  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14258  {
14259  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14260  if(AVEntry.FormatType == TimeLoc)
14261  {
14262  if(AVEntry.EventTime != TDateTime(-1))
14263  {
14264  throw Exception("Timetable error, TimeLoc event has EventTime not -1 for " + TDEntry.HeadCode);
14265  }
14266  if((AVEntry.ArrivalTime == TDateTime(-1)) && (AVEntry.DepartureTime == TDateTime(-1)))
14267  {
14268  throw Exception("Timetable error, TimeLoc event has neither arrival nor departure time set for " + TDEntry.HeadCode);
14269  }
14270  }
14271  if(AVEntry.FormatType == TimeTimeLoc)
14272  {
14273  if(AVEntry.EventTime != TDateTime(-1))
14274  {
14275  throw Exception("Timetable error, TimeTimeLoc event has EventTime not -1 for " + TDEntry.HeadCode);
14276  }
14277  if((AVEntry.ArrivalTime == TDateTime(-1)) || (AVEntry.DepartureTime == TDateTime(-1)))
14278  {
14279  throw Exception("Timetable error, TimeTimeLoc event has either arrival or departure time not set for " + TDEntry.HeadCode);
14280  }
14281  }
14282  if((AVEntry.FormatType == TimeCmd) || (AVEntry.FormatType == TimeCmdHeadCode) || (AVEntry.FormatType == StartNew) ||
14283  (AVEntry.FormatType == SNTShuttle) || (AVEntry.FormatType == SNSShuttle) || (AVEntry.FormatType == FNSNonRepeatToShuttle) ||
14284  (AVEntry.FormatType == FSHNewService) || (AVEntry.FormatType == PassTime))
14285  {
14286  if(AVEntry.EventTime == TDateTime(-1))
14287  {
14288  throw Exception("Timetable error, Cmd or PassTime event has EventTime not set for " + TDEntry.HeadCode);
14289  }
14290  if((AVEntry.ArrivalTime != TDateTime(-1)) || (AVEntry.DepartureTime != TDateTime(-1)))
14291  {
14292  throw Exception("Timetable error, Cmd or PassTime event has either arrival or departure time set for " + TDEntry.HeadCode);
14293  }
14294  }
14295  if(AVEntry.FormatType == Repeat)
14296  {
14297  if((AVEntry.EventTime != TDateTime(-1)) || (AVEntry.ArrivalTime != TDateTime(-1)) || (AVEntry.DepartureTime != TDateTime(-1)))
14298  {
14299  throw Exception("Timetable error, Repeat event has a time set for " + TDEntry.HeadCode);
14300  }
14301  }
14302  }
14303  }
14304 
14305  // check times stay same or increase, note that can have time of 0 if include midnight
14306  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14307  {
14308  TDateTime CurrentTime = TTClockTime; // the timetable start time
14309  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14310  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14311  {
14312  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14313  if(AVEntry.FormatType == Repeat)
14314  {
14315  break;
14316  }
14317  if(AVEntry.FormatType == FinRemHere)
14318  {
14319  break;
14320  }
14321  if(AVEntry.FormatType == TimeTimeLoc)
14322  {
14323  if(AVEntry.DepartureTime < AVEntry.ArrivalTime)
14324  {
14325  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival and departure has a later arrival than departure time for: " +
14326  TDEntry.HeadCode);
14327  TrainDataVector.clear();
14328  Utilities->CallLogPop(813);
14329  return(false);
14330  }
14331  if(AVEntry.ArrivalTime < CurrentTime)
14332  {
14333  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival and departure has too early an arrival time for: " +
14334  TDEntry.HeadCode);
14335  TrainDataVector.clear();
14336  Utilities->CallLogPop(814);
14337  return(false);
14338  }
14339  CurrentTime = AVEntry.DepartureTime;
14340  continue;
14341  }
14342  if(AVEntry.FormatType == TimeLoc)
14343  {
14344  if(AVEntry.ArrivalTime >= TDateTime(0))
14345  {
14346  if(AVEntry.ArrivalTime < CurrentTime)
14347  {
14348  SecondPassMessage(GiveMessages, "Error in timetable - a timed location event has a time that is too early for: " + TDEntry.HeadCode);
14349  TrainDataVector.clear();
14350  Utilities->CallLogPop(815);
14351  return(false);
14352  }
14353  CurrentTime = AVEntry.ArrivalTime;
14354  }
14355  else
14356  {
14357  if(AVEntry.DepartureTime < CurrentTime)
14358  // both may be 0 legitimately so must allow for this
14359  {
14360  SecondPassMessage(GiveMessages, "Error in timetable - a timed location event has a time that is too early for: " + TDEntry.HeadCode);
14361  TrainDataVector.clear();
14362  Utilities->CallLogPop(816);
14363  return(false);
14364  }
14365  CurrentTime = AVEntry.DepartureTime;
14366  }
14367  continue;
14368  }
14369  if(AVEntry.EventTime < CurrentTime)
14370  // all others have EventTime set
14371  {
14372  SecondPassMessage(GiveMessages, "Error in timetable - a train event has a time that is set too early for: " + TDEntry.HeadCode +
14373  ", may be before timetable start time");
14374  TrainDataVector.clear();
14375  Utilities->CallLogPop(835);
14376  return(false);
14377  }
14378  CurrentTime = AVEntry.EventTime;
14379  continue;
14380  }
14381  }
14382 
14383  // check locations consistent
14384  AnsiString LastLocationName = "";
14385 
14386  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14387  {
14388  bool LastEntryIsAnArrival = false;
14389  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14390  // first deal with moving Snt entries (all else stopped)
14391  if((TrainDataVector.at(x).ActionVector.at(0).Command == "Snt") && (TrainDataVector.at(x).ActionVector.at(0).LocationType == EnRoute))
14392  {
14393  LastEntryIsAnArrival = false;
14394  LastLocationName = TrainDataVector.at(x).ActionVector.at(0).LocationName; // should be ""
14395  if(LastLocationName != "")
14396  {
14397  throw Exception("Timetable error, moving Snt event has LocationName set for " + TDEntry.HeadCode);
14398  }
14399  for(unsigned int y = 1; y < TrainDataVector.at(x).ActionVector.size();
14400  y++) // note that immediate successor to a moving Snt can only be a Moving type
14401  {
14402  // if it's a SignallerControl entry then the condition isn't met
14403  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14404  if(AVEntry.FormatType == Repeat)
14405  {
14406  break; // repeat = reached end (+allows repeat after signaller controlled entry)
14407  }
14408  else if((AVEntry.FormatType == TimeCmdHeadCode) || (AVEntry.FormatType == FNSNonRepeatToShuttle))
14409  {
14410  if(AVEntry.LocationName != LastLocationName)
14411  {
14412  SecondPassMessage(GiveMessages, "Error in timetable - a location event is inconsistent for: " + TDEntry.HeadCode + " && " +
14413  AVEntry.Command);
14414  TrainDataVector.clear();
14415  Utilities->CallLogPop(823);
14416  return(false);
14417  }
14418  }
14419  else if((AVEntry.FormatType == TimeCmd) || (AVEntry.FormatType == TimeCmdDescription))
14420  // cdt is the only TimeCmd & dsc is the only TimeCmdDescription
14421  {
14422  if(AVEntry.LocationName != LastLocationName)
14423  {
14424  SecondPassMessage(GiveMessages, "Error in timetable - a location event is inconsistent for: " + TDEntry.HeadCode + " && " +
14425  AVEntry.Command);
14426  TrainDataVector.clear();
14427  Utilities->CallLogPop(824);
14428  return(false);
14429  }
14430  }
14431  else if(AVEntry.FormatType == TimeTimeLoc)
14432  {
14433  if((AVEntry.LocationName == LastLocationName) && TTEditPanelVisible) //changed at v2.6.0 to allow loops & consecutive same locs
14434  // last entry must be a departure or would have failed earlier
14435  {
14436  TwoLocationList.insert(TwoLocationList.end(), TDEntry.ServiceReference); //added at v2.9.1
14437  TwoLocationFlag = true;
14438 // ShowMessage("Two or more locations are the same without a change of direction between them. Please correct if this is an error.\n\nThis warning will not be shown again.");
14439 // TwoOrMoreLocationsWarningGiven = true;
14440  }
14441  LastLocationName = AVEntry.LocationName;
14442  LastEntryIsAnArrival = false;
14443  }
14444  else if(AVEntry.FormatType == TimeLoc)
14445  {
14446  if(LastEntryIsAnArrival && (AVEntry.LocationName != LastLocationName))
14447  {
14448  SecondPassMessage(GiveMessages,
14449  "Error in timetable - a location event for a timed departure is different from the arrival location for: " + TDEntry.HeadCode);
14450  TrainDataVector.clear();
14451  Utilities->CallLogPop(826);
14452  return(false);
14453  }
14454  else if(!LastEntryIsAnArrival && (AVEntry.LocationName == LastLocationName))
14455  {
14456  SecondPassMessage(GiveMessages,
14457  "Error in timetable - a location event for a timed arrival is the same as the earlier departure location for: " + TDEntry.HeadCode);
14458  TrainDataVector.clear();
14459  Utilities->CallLogPop(827);
14460  return(false);
14461  }
14462  LastLocationName = AVEntry.LocationName;
14463  LastEntryIsAnArrival = !LastEntryIsAnArrival;
14464  }
14465  }
14466  }
14467  else // all stationary starting entries
14468  {
14469  LastEntryIsAnArrival = true;
14470  LastLocationName = TrainDataVector.at(x).ActionVector.at(0).LocationName;
14471  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14472  {
14473  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14474  if(AVEntry.FormatType == Repeat)
14475  {
14476  break;
14477  }
14478  else if((AVEntry.FormatType == TimeCmdHeadCode) || (AVEntry.FormatType == FNSNonRepeatToShuttle))
14479  // no need to add anything for shuttle starts since they are at loc (0) anyway
14480  {
14481  if(AVEntry.LocationName != LastLocationName)
14482  {
14483  SecondPassMessage(GiveMessages, "Error in timetable - a location event is inconsistent for: " + TDEntry.HeadCode + " && " +
14484  AVEntry.Command);
14485  TrainDataVector.clear();
14486  Utilities->CallLogPop(828);
14487  return(false);
14488  }
14489  }
14490  else if(AVEntry.FormatType == TimeCmd)
14491  // cdt is the only TimeCmd
14492  {
14493  if(AVEntry.LocationName != LastLocationName)
14494  {
14495  SecondPassMessage(GiveMessages, "Error in timetable - a location event is inconsistent for: " + TDEntry.HeadCode + " && " +
14496  AVEntry.Command);
14497  TrainDataVector.clear();
14498  Utilities->CallLogPop(829);
14499  return(false);
14500  }
14501  }
14502  else if(AVEntry.FormatType == TimeTimeLoc)
14503  {
14504  if((AVEntry.LocationName == LastLocationName) && TTEditPanelVisible) //changed at v2.6.0 to allow loops & consecutive same locs
14505  // last entry must be a departure or would have failed earlier
14506  {
14507  TwoLocationList.insert(TwoLocationList.end(), TDEntry.ServiceReference); //added at v2.9.1
14508  TwoLocationFlag = true;
14509 // ShowMessage("Two or more locations are the same without a change of direction between them. Please correct if this is an error.\n\nThis warning will not be shown again.");
14510 // TwoOrMoreLocationsWarningGiven = true;
14511  }
14512  LastLocationName = AVEntry.LocationName;
14513  LastEntryIsAnArrival = false;
14514  }
14515  else if(AVEntry.FormatType == TimeLoc)
14516  {
14517  if(LastEntryIsAnArrival && (AVEntry.LocationName != LastLocationName))
14518  {
14519  SecondPassMessage(GiveMessages,
14520  "Error in timetable - a location event for a timed departure is different from the arrival location for: " + TDEntry.HeadCode);
14521  TrainDataVector.clear();
14522  Utilities->CallLogPop(831);
14523  return(false);
14524  }
14525  if(!LastEntryIsAnArrival && (AVEntry.LocationName == LastLocationName) && !TwoOrMoreLocationsWarningGiven)
14526  {
14527  SecondPassMessage(GiveMessages,
14528  "A location event for a timed arrival is the same as the earlier departure location for: " + TDEntry.HeadCode + ". Please correct if this is an error.\n\nThis warning will not be shown again.");
14530 // TrainDataVector.clear();
14531 // Utilities->CallLogPop(832);
14532 // return false;
14533  }
14534  LastLocationName = AVEntry.LocationName;
14535  LastEntryIsAnArrival = !LastEntryIsAnArrival;
14536  }
14537  }
14538  }
14539  }
14540 
14541  // Check same location doesn't appear twice before a cdt except for separate arr & dep TimeLocs (just a potential error warning given in v2.6.0)
14542  // i.e. same location can appear in any number of consecutive entries but once changed couldn't repeat before a direction change prior to v2.6.0
14543  AnsiString LocationNameToBeChecked = "";
14544 
14545  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14546  {
14547  const TTrainDataEntry & TDEntry = TrainDataVector.at(x);
14548  unsigned int y = 0;
14549  const TActionVectorEntry &AVEntry0 = TDEntry.ActionVector.at(0);
14550  // first discard unlocated Snt entries as they don't have location name set
14551  if((AVEntry0.Command == "Snt") && (AVEntry0.LocationType == EnRoute))
14552  {
14553  y = 1;
14554  }
14555  while(y < TDEntry.ActionVector.size())
14556  // need to check each location name separately in turn, skipped for SignallerControl entries
14557  {
14558  if((TDEntry.ActionVector.at(y).Command == "Fer") || (TDEntry.ActionVector.at(y).FormatType == Repeat))
14559  {
14560  break; // out of the 'while' loop since have reached the end & 'Fer' & 'Repeat' have no location name set
14561  }
14562  LocationNameToBeChecked = TDEntry.ActionVector.at(y).LocationName;
14563  for(unsigned int z = y; z < TDEntry.ActionVector.size(); z++)
14564  {
14565  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(z);
14566  if((AVEntry.Command == "Fer") || (AVEntry.FormatType == Repeat))
14567  {
14568  break; // out of the 'z' loop since have reached the end & 'Fer' & 'Repeat' have no location name set
14569  }
14570  if(AVEntry.Command == "cdt")
14571  {
14572  break; // out of the 'z' loop since the check is only valid up to a change of direction
14573  }
14574  if(AVEntry.LocationName == LocationNameToBeChecked)
14575  {
14576  continue; // keep going while name same
14577  }
14578  if(AVEntry.LocationName != LocationNameToBeChecked)
14579  // if name different check forwards to see if repeats
14580  {
14581  for(unsigned int a = z; a < TDEntry.ActionVector.size(); a++)
14582  {
14583  if(TDEntry.ActionVector.at(a).Command == "cdt")
14584  {
14585  break; // out of the 'a' & 'z' loops since the check is only valid up to a change of direction
14586  }
14587  if((TDEntry.ActionVector.at(a).LocationName == LocationNameToBeChecked) && TTEditPanelVisible) //changed at v2.6.0 to allow loops & consecutive same locs
14588  {
14589  TwoLocationList.insert(TwoLocationList.end(), TDEntry.ServiceReference); //added at v2.9.1
14590  TwoLocationFlag = true;
14591 // ShowMessage("Two or more locations are the same without a change of direction between them. Please correct if this is an error.\n\nThis warning will not be shown again.");
14592 // TwoOrMoreLocationsWarningGiven = true;
14593  }
14594  }
14595  break; // out of the 'z' loop since have checked 'a' as far as need to
14596  }
14597  }
14598  y++;
14599  }
14600  }
14601  if(TwoLocationFlag) //messages for this moved to InterfaceUnit - separate box listing potential errors and asking user to check
14602  {
14603  TwoLocationList.sort(); //need to sort first in alphabetical order to ensure all duplictes removed
14604  TwoLocationList.unique(); //remove duplicates
14605  }
14606 
14607  // check all locations except unlocated 'Snt' & 'Fer' have LocationName set
14608  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14609  {
14610  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14611  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14612  {
14613  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14614  if((AVEntry.LocationName == "") && (AVEntry.Command != "Snt") && (AVEntry.Command != "Fer") && (AVEntry.FormatType != Repeat))
14615  {
14616  throw Exception("Error, non- 'Snt', 'Fer' or Repeat event doesn't have a location name set for " + TDEntry.HeadCode);
14617  }
14618  AnsiString LocName = "";
14619  // dummy, only used so can call IsSNTEntryLocated
14620  if((AVEntry.Command == "Snt") && (IsSNTEntryLocated(1, TrainDataVector.at(x), LocName)))
14621  {
14622  if(AVEntry.LocationName == "")
14623  {
14624  throw Exception("Error, 'Snt' event at a stop location doesn't have a location name set for " + TDEntry.HeadCode);
14625  }
14626  }
14627  if((AVEntry.Command == "Snt") && !(IsSNTEntryLocated(2, TrainDataVector.at(x), LocName)))
14628  {
14629  if(AVEntry.LocationName != "")
14630  {
14631  throw Exception("Error, 'Snt' unlocated event has a location name set for " + TDEntry.HeadCode);
14632  }
14633  }
14634  }
14635  }
14636 
14637 /* Check a split to 'x' doesn't again split to 'x' (anywhere, not just for one train, since headcodes can be duplicated)
14638  Check each joined by train not joined by same train again (anywhere, not just for one train, since headcodes can be duplicated)
14639  Check each change of headcode not repeated anywhere else (anywhere, not just for one train, since headcodes can be duplicated)
14640 
14641  i.e. check everywhere where there is an 'OtherHeadCode' that it matches once only with its reference (both ways) + set
14642  the OtherHeadCodeStartingEntryPtr pointers where appropriate + train information for splits & new services
14643 
14644  BUT need to separate the shuttles from non-shuttles, because can have two trains reference each other in both forms,
14645  eg 2F44 Sns-sh ends in Fns to 2F45, & Sns 2F45 ends in Fns-sh to 2F44. Here 2F45 is the 'OtherHeadCode' for both
14646  Sns-sh & Fns in train 2F44, & 2F44 is the 'OtherHeadCode' for both Sns & Fns-sh in train 2F45.
14647 */
14648  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // new test to ensure no duplicate links at all, other checks ensure none for shuttles,
14649  {
14650  // non-shuttles & non-repeating links separately, but don't check that there isn't a
14651  // duplicate between a non-repeating shuttle and another - leave original tests in as
14652  // these also set the pointers
14653  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14654  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14655  {
14656  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14657  if(AVEntry.OtherHeadCode != "")
14658  {
14659  if(!CheckForDuplicateCrossReferences(0, TDEntry.HeadCode, AVEntry.OtherHeadCode, GiveMessages))
14660  {
14661  Utilities->CallLogPop(1584);
14662  return(false); // error message given in called function
14663  }
14664  }
14665  if(AVEntry.NonRepeatingShuttleLinkHeadCode != "")
14666  {
14667  if(!CheckForDuplicateCrossReferences(1, TDEntry.HeadCode, AVEntry.NonRepeatingShuttleLinkHeadCode, GiveMessages))
14668  {
14669  Utilities->CallLogPop(1585);
14670  return(false); // error message given in called function
14671  }
14672  }
14673  }
14674  }
14675 
14676  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14677  {
14678  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14679  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14680  {
14681  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14682  if((AVEntry.Command != "Sns-sh") && (AVEntry.Command != "Snt-sh") && (AVEntry.Command != "Fns-sh") && (AVEntry.Command != "Frh-sh"))
14683  {
14684  if(AVEntry.OtherHeadCode != "")
14685  {
14686  if(!CheckCrossReferencesAndSetData(0, TDEntry.HeadCode, AVEntry.OtherHeadCode, false, true, GiveMessages))
14687  // false = non-shuttle
14688  {
14689  Utilities->CallLogPop(864);
14690  return(false); // error message given in called function
14691  }
14692  }
14693  }
14694  }
14695  }
14696 
14697  // now repeat the check just for the shuttles
14698  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14699  {
14700  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14701  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14702  {
14703  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14704  if((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "Frh-sh"))
14705  {
14706  if(AVEntry.OtherHeadCode != "")
14707  {
14708  if(!CheckCrossReferencesAndSetData(1, TDEntry.HeadCode, AVEntry.OtherHeadCode, true, true, GiveMessages))
14709  // true = shuttle
14710  {
14711  Utilities->CallLogPop(1100);
14712  return(false); // error message given in called function
14713  }
14714  }
14715  }
14716  }
14717  }
14718 
14719  // check for proper non-repeating link cross references and that they have no repeats & that times are consistent
14720  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14721  {
14722  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14723  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14724  {
14725  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14726  if(AVEntry.NonRepeatingShuttleLinkHeadCode != "")
14727  {
14728  if(!CheckNonRepeatingShuttleLinksAndSetData(0, TDEntry.HeadCode, AVEntry.NonRepeatingShuttleLinkHeadCode, true, GiveMessages))
14729  {
14730  Utilities->CallLogPop(1060);
14731  return(false); // error message given in called function
14732  }
14733  }
14734  }
14735  }
14736 
14737  // check that each shuttle start ends either in Fns or Fxx-sh (though a single service can't end in Fxx-sh), and that
14738  // when the Fxx-sh is reached it references the original start and not another shuttle - not allowed to link two shuttles,
14739  // don't ever need to and as designed would skip repeats
14740  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14741  {
14742  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14743  {
14744  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14745  if((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh"))
14746  {
14747  if(!CheckShuttleServiceIntegrity(0, &(TrainDataVector.at(x)), GiveMessages))
14748  {
14749  Utilities->CallLogPop(1090);
14750  return(false); // error message given in called function
14751  }
14752  }
14753  }
14754  }
14755 
14756  // check all entries have all types set to something
14757  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14758  {
14759  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14760  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14761  {
14762  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14763  if(AVEntry.FormatType == NoFormat)
14764  {
14765  throw Exception("Error - timetable ActionVector event no. " + AnsiString(y) + " has FormatType unset for: " + TDEntry.HeadCode);
14766  }
14767  else if(AVEntry.SequenceType == NoSequence)
14768  {
14769  throw Exception("Error - timetable ActionVector event no. " + AnsiString(y) + " has SequenceType unset for: " + TDEntry.HeadCode);
14770  }
14771  else if(AVEntry.LocationType == NoLocation)
14772  {
14773  throw Exception("Error - timetable ActionVector event no. " + AnsiString(y) + " has LocationType unset for: " + TDEntry.HeadCode);
14774  }
14775  else if(AVEntry.ShuttleLinkType == NoShuttleLink)
14776  {
14777  throw Exception("Error - timetable ActionVector event no. " + AnsiString(y) + " has ShuttleLinkType unset for: " + TDEntry.HeadCode);
14778  }
14779  }
14780  }
14781 
14782  // all OK if reach here, so set up the TrainOperatingDataVector (already has one entry) & NumberOfTrains
14783  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14784  {
14785  TTrainDataEntry & TDEntry = TrainDataVector.at(x);
14786  // non-const reference so can alter content
14787  TTrainOperatingData TData;
14788  const TActionVectorEntry &LastAVEntry = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
14789  if(LastAVEntry.FormatType == Repeat) // check if a repeat
14790  {
14791 /*
14792  class TTrainOperatingData
14793  {
14794  public:
14795  int TrainID; - default, set at construction
14796  TActionEventType EventReported; used during operation
14797  TRunningEntry RunningEntry; - default, set at construction
14798  TTrainOperatingData() {TrainID = -1; EventReported= NoEvent; RunningEntry=NotStarted;} //constructor, values set to defaults
14799  };
14800 */
14801  TDEntry.NumberOfTrains = LastAVEntry.NumberOfRepeats + 1;
14802  for(int y = 1; y < TDEntry.NumberOfTrains; y++)
14803  {
14804  TDEntry.TrainOperatingDataVector.push_back(TData);
14805  }
14806  }
14807  else
14808  {
14809  TDEntry.NumberOfTrains = 1;
14810  }
14811  }
14812 
14813  // check that don't include any Continuation names
14814  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14815  {
14816  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14817  {
14818  AnsiString LocName = TrainDataVector.at(x).ActionVector.at(y).LocationName;
14819  AnsiString HC = TrainDataVector.at(x).HeadCode;
14820  if(LocName != "")
14821  {
14822  if(Track->ContinuationNameMap.find(LocName) != Track->ContinuationNameMap.end())
14823  {
14824  SecondPassMessage(GiveMessages, "Error in timetable - continuation names (" + LocName + ") must not be included, see service " + HC);
14825  TrainDataVector.clear();
14826  Utilities->CallLogPop(1578);
14827  return(false);
14828  }
14829  }
14830  }
14831  }
14832 
14833  // check that all repeat times below 96h
14834  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14835  {
14836  int NumRepeats = (TrainDataVector.at(x).NumberOfTrains) - 1;
14837  int IncMinutes = 0;
14838  if(TrainDataVector.at(x).ActionVector.at(TrainDataVector.at(x).ActionVector.size() - 1).FormatType == Repeat)
14839  {
14840  IncMinutes = TrainDataVector.at(x).ActionVector.at(TrainDataVector.at(x).ActionVector.size() - 1).RearStartOrRepeatMins;
14841  }
14842  else
14843  {
14844  continue; // basic times already checked in CheckTimeValidity
14845  }
14846  AnsiString HC = TrainDataVector.at(x).HeadCode;
14847  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14848  {
14849  if((double)TrainDataVector.at(x).ActionVector.at(y).EventTime > -1)
14850  {
14851  if(((double)GetRepeatTime(32, TrainDataVector.at(x).ActionVector.at(y).EventTime, NumRepeats, IncMinutes) >= 3.9994))
14852  {
14853  SecondPassMessage(GiveMessages, "Error in timetable - a repeat time exceeds 95h 59m, see service " + HC); // 3d 23h 59m = 3.9993055556
14854  TrainDataVector.clear();
14855  Utilities->CallLogPop(1818);
14856  return(false);
14857  }
14858  }
14859  if((double)TrainDataVector.at(x).ActionVector.at(y).ArrivalTime > -1)
14860  {
14861  if(((double)GetRepeatTime(33, TrainDataVector.at(x).ActionVector.at(y).EventTime, NumRepeats, IncMinutes) >= 3.9994))
14862  // 3d 23h 59m = 3.9993055556
14863  {
14864  SecondPassMessage(GiveMessages, "Error in timetable - a repeat event time exceeds 95h 59m, see service " + HC);
14865  TrainDataVector.clear();
14866  Utilities->CallLogPop(1819);
14867  return(false);
14868  }
14869  }
14870  if((double)TrainDataVector.at(x).ActionVector.at(y).DepartureTime > -1)
14871  {
14872  if(((double)GetRepeatTime(34, TrainDataVector.at(x).ActionVector.at(y).EventTime, NumRepeats, IncMinutes) >= 3.9994))
14873  // 3d 23h 59m = 3.9993055556
14874  {
14875  SecondPassMessage(GiveMessages, "Error in timetable - a repeat event time exceeds 95h 59m, see service " + HC);
14876  TrainDataVector.clear();
14877  Utilities->CallLogPop(1820);
14878  return(false);
14879  }
14880  }
14881  }
14882  }
14883 
14884  // Now that all set up change any extended headcodes back to ordinary headcodes
14885  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14886  {
14887  StripExcessFromHeadCode(0, TrainDataVector.at(x).HeadCode);
14888  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14889  {
14890  StripExcessFromHeadCode(1, TrainDataVector.at(x).ActionVector.at(y).OtherHeadCode);
14891  StripExcessFromHeadCode(2, TrainDataVector.at(x).ActionVector.at(y).NonRepeatingShuttleLinkHeadCode);
14892  }
14893  }
14894 
14895  // SaveTrainDataVectorToFile(0);//for testing purposes
14897  Utilities->CallLogPop(782);
14898  return(true);
14899 }
14900 
14901 // ---------------------------------------------------------------------------
14902 // Moving successors: TimeLoc arr/TimeTimeLoc/pas/Fer;
14904 {
14905  return ((AVEntry.FormatType == TimeLoc) || (AVEntry.FormatType == TimeTimeLoc) || (AVEntry.Command == "pas") || (AVEntry.Command == "Fer"));
14906 }
14907 
14908 // ---------------------------------------------------------------------------
14909 // AtLoc successors: TimeLoc dep/jbo/fsp/rsp/cdt/dsc/Frh/Fns/Fjo/Frh-sh/Fns-sh/F-nshs;
14911 {
14912  return ((AVEntry.FormatType == TimeLoc) || (AVEntry.Command == "jbo") || (AVEntry.Command == "fsp") || (AVEntry.Command == "rsp") ||
14913  (AVEntry.Command == "cdt") || (AVEntry.Command == "dsc") || (AVEntry.Command == "Frh") || (AVEntry.Command == "Fns") || (AVEntry.Command == "Fjo") || (AVEntry.Command == "Frh-sh") ||
14914  (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "F-nshs"));
14915 }
14916 
14917 // ---------------------------------------------------------------------------
14918 
14919 void TTrainController::StripExcessFromHeadCode(int Caller, AnsiString &HeadCode)
14920 {
14921  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",StripExcessFromHeadCode," + HeadCode);
14922  if(HeadCode.Length() > 4) // ignore otherwise
14923  {
14924  HeadCode = HeadCode.SubString(HeadCode.Length() - 3, 4);
14925  }
14926  Utilities->CallLogPop(1593);
14927 }
14928 
14929 // ---------------------------------------------------------------------------
14930 
14931 bool TTrainController::CheckForDuplicateCrossReferences(int Caller, AnsiString MainHeadCode, AnsiString SecondHeadCode, bool GiveMessages)
14932 {
14933  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckForDuplicateCrossReferences," + MainHeadCode + "," +
14934  SecondHeadCode);
14935  int ForwardCount = 0;
14936  int ReverseCount = 0;
14937 
14938  if(MainHeadCode == SecondHeadCode)
14939  {
14940  SecondPassMessage(GiveMessages, "Error in timetable - Service " + MainHeadCode + " has an event that references itself");
14941  TrainDataVector.clear();
14942  Utilities->CallLogPop(1594);
14943  return(false);
14944  }
14945  // forward check
14946  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14947  {
14948  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14949  if(TDEntry.HeadCode == MainHeadCode)
14950  {
14951  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14952  {
14953  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14954  if(AVEntry.OtherHeadCode == SecondHeadCode)
14955  {
14956  ForwardCount++;
14957  }
14958  if(AVEntry.NonRepeatingShuttleLinkHeadCode == SecondHeadCode)
14959  // need own check in case both 'Other' & 'NonRepeating' have same headcode
14960  {
14961  ForwardCount++;
14962  }
14963  }
14964  }
14965  }
14966  if(ForwardCount == 0)
14967  // this is an exception because the headcodes are selected in the same order as the forward check
14968  {
14969  throw Exception("Error, ForwardCount == 0 in CheckForDuplicateCrossReferences after called with found values");
14970  }
14971  if(ForwardCount > 2)
14972  // can have 2 if one is Sns-sh linking from another leg of the shuttle, and Fns links out to that same leg
14973  {
14974  SecondPassMessage(GiveMessages, "Error in timetable - found more than two references to " + SecondHeadCode + " from a train whose headcode is " +
14975  MainHeadCode + ". Check the service cross references from each service, and check whether one or other service is listed twice or more.");
14976  TrainDataVector.clear();
14977  Utilities->CallLogPop(1587);
14978  return(false);
14979  }
14980  // reverse check
14981  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14982  {
14983  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14984  if(TDEntry.HeadCode == SecondHeadCode)
14985  {
14986  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14987  {
14988  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14989  if(AVEntry.OtherHeadCode == MainHeadCode)
14990  {
14991  ReverseCount++;
14992  }
14993  if(AVEntry.NonRepeatingShuttleLinkHeadCode == MainHeadCode)
14994  {
14995  ReverseCount++;
14996  }
14997  }
14998  }
14999  }
15000 
15001  if(ReverseCount == 0)
15002  {
15003  SecondPassMessage(GiveMessages, "Error in timetable - cross reference missing in either " + MainHeadCode + " or " + SecondHeadCode);
15004  TrainDataVector.clear();
15005  Utilities->CallLogPop(1588);
15006  return(false);
15007  }
15008  if(ReverseCount > 2)
15009  // can have 2 if one is a second shuttle leg with a link in from Fns, and it links out to the same service with Fxx-sh
15010  {
15011  SecondPassMessage(GiveMessages, "Error in timetable - found more than two references to " + MainHeadCode + " from a train whose headcode is " +
15012  SecondHeadCode + ". Check the service cross references from each service, and check whether one or other service is listed twice or more.");
15013  TrainDataVector.clear();
15014  Utilities->CallLogPop(1589);
15015  return(false);
15016  }
15017  if(ForwardCount != ReverseCount)
15018  {
15019  SecondPassMessage(GiveMessages, "Error in timetable - " + MainHeadCode + " has a different number of references to " + SecondHeadCode +
15020  " than the other way round");
15021  TrainDataVector.clear();
15022  Utilities->CallLogPop(1610);
15023  return(false);
15024  }
15025  Utilities->CallLogPop(1590);
15026  return(true);
15027 }
15028 
15029 // ---------------------------------------------------------------------------
15030 
15031 bool TTrainController::CheckCrossReferencesAndSetData(int Caller, AnsiString MainHeadCode, AnsiString OtherHeadCode, bool Shuttle, bool SetDataAndCheckLocations, bool GiveMessages)
15032 /* Return false for no find or more than one find, check correct types of link
15033  First run through all trains whose headcode is the MainHeadCode (may be > 1) & for each entry whose
15034  'other' is OtherHeadCode increment a forward counter. Keep a pointer to the 'OtherHeadCode' entry for use later
15035  Must be exactly 1 forward count. NB Forward relates to MainHeadCode
15036  Then do the same in reverse.
15037  Using the pointers check the event times, then check that the locations & commands match - if main is a split then other must be Sfs;
15038  if main is Fns other must be Sns; if main is jbo other must be Fjo.
15039  Also check platform lengths OK for a split location (call to Track function for this - at least one platform at location has to be long
15040  enough). If all succeeds so far set the relevant OtherHeadCodeStartingEntryPtr to the new service starting point + train information
15041  for Sfs & Sns services. Finally check the repeat entries if present are consistent
15042 
15043  Check all except the NonRepeatingShuttleLinkHeadCodes, which only occur from F-nshs to Sns-sh, and from Fns-sh to
15044  Sns-fsh. All others should check out OK, but check shuttles & non-shuttles separately.
15045 
15046  /NB prohibit main & other headcodes being same, causes probs in failing to recognise locations
15047 */
15048 
15049 {
15050  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckCrossReferencesAndSetData," + MainHeadCode + "," + OtherHeadCode);
15051  int ForwardCount = 0;
15052  int ReverseCount = 0;
15053  unsigned int ForwardTDVectorNumber, ReverseTDVectorNumber;
15054  TActionVectorEntry *ReverseEntryPtr = 0, *ForwardEntryPtr = 0;
15055  TTrainDataEntry *MainTrainDataPtr = 0;
15056  TTrainDataEntry *OtherTrainDataPtr = 0;
15057 
15058  // forward check
15059  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15060  {
15061  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15062  if(TDEntry.HeadCode == MainHeadCode)
15063  {
15064  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15065  {
15066  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15067  if(!Shuttle && (AVEntry.Command != "Sns-sh") && (AVEntry.Command != "Snt-sh") && (AVEntry.Command != "Fns-sh") && (AVEntry.Command != "Frh-sh"))
15068  {
15069  if(AVEntry.OtherHeadCode == OtherHeadCode)
15070  {
15071  MainTrainDataPtr = &TrainDataVector.at(x);
15072  ForwardEntryPtr = &AVEntry;
15073  ForwardCount++;
15074  ForwardTDVectorNumber = x;
15075  }
15076  }
15077  else if(Shuttle && ((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh") || (AVEntry.Command == "Fns-sh") ||
15078  (AVEntry.Command == "Frh-sh")))
15079  {
15080  if(AVEntry.OtherHeadCode == OtherHeadCode)
15081  {
15082  MainTrainDataPtr = &TrainDataVector.at(x);
15083  ForwardEntryPtr = &AVEntry;
15084  ForwardCount++;
15085  ForwardTDVectorNumber = x;
15086  }
15087  }
15088  }
15089  }
15090  }
15091  if(ForwardCount == 0)
15092  // this is an exception because the headcodes are selected in the same order as the forward check
15093  {
15094  throw Exception("Error, ForwardCount == 0 in CheckCrossReferencesAndSetData after called with found values");
15095  }
15096  if(ForwardCount > 1)
15097  {
15098  SecondPassMessage(GiveMessages, "Error in timetable - found more than one reference to " + OtherHeadCode + " from a train whose headcode is " +
15099  MainHeadCode);
15100  TrainDataVector.clear();
15101  Utilities->CallLogPop(836);
15102  return(false);
15103  }
15104  // reverse check
15105  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15106  {
15107  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15108  if(TDEntry.HeadCode == OtherHeadCode)
15109  {
15110  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15111  {
15112  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15113  if(!Shuttle && (AVEntry.Command != "Sns-sh") && (AVEntry.Command != "Snt-sh") && (AVEntry.Command != "Fns-sh") && (AVEntry.Command != "Frh-sh"))
15114  {
15115  if(AVEntry.OtherHeadCode == MainHeadCode)
15116  {
15117  OtherTrainDataPtr = &TrainDataVector.at(x);
15118  ReverseCount++;
15119  ReverseEntryPtr = &AVEntry;
15120  ReverseTDVectorNumber = x;
15121  }
15122  }
15123  else if(Shuttle && ((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "Frh-sh")))
15124  {
15125  if(AVEntry.OtherHeadCode == MainHeadCode)
15126  {
15127  OtherTrainDataPtr = &TrainDataVector.at(x);
15128  ReverseCount++;
15129  ReverseEntryPtr = &AVEntry;
15130  ReverseTDVectorNumber = x;
15131  }
15132  }
15133  }
15134  }
15135  }
15136 
15137  if(ReverseCount == 0)
15138  {
15139  SecondPassMessage(GiveMessages, "Error in timetable - cross reference missing in either " + MainHeadCode + " or " + OtherHeadCode);
15140  TrainDataVector.clear();
15141  Utilities->CallLogPop(837);
15142  return(false);
15143  }
15144  if(ReverseCount > 1)
15145  {
15146  SecondPassMessage(GiveMessages, "Error in timetable - found more than one reference to " + MainHeadCode + " from a train whose headcode is " +
15147  OtherHeadCode);
15148  TrainDataVector.clear();
15149  Utilities->CallLogPop(838);
15150  return(false);
15151  }
15152  // these will all be false for !Shuttle
15153  bool ForwardShuttleStart = ((ForwardEntryPtr->Command == "Sns-sh") || (ForwardEntryPtr->Command == "Snt-sh"));
15154  bool ForwardShuttleFinish = ((ForwardEntryPtr->Command == "Fns-sh") || (ForwardEntryPtr->Command == "Frh-sh"));
15155  bool ReverseShuttleStart = ((ReverseEntryPtr->Command == "Sns-sh") || (ReverseEntryPtr->Command == "Snt-sh"));
15156  bool ReverseShuttleFinish = ((ReverseEntryPtr->Command == "Fns-sh") || (ReverseEntryPtr->Command == "Frh-sh"));
15157 
15158  if(Shuttle && MainTrainDataPtr->ActionVector.back().FormatType != Repeat)
15159  {
15160  SecondPassMessage(GiveMessages, "Error in timetable - shuttle train " + MainHeadCode + " does not have a repeat");
15161  TrainDataVector.clear();
15162  Utilities->CallLogPop(1058);
15163  return(false);
15164  }
15165  if(Shuttle && OtherTrainDataPtr->ActionVector.back().FormatType != Repeat)
15166  {
15167  SecondPassMessage(GiveMessages, "Error in timetable - shuttle train " + OtherHeadCode + " does not have a repeat");
15168  TrainDataVector.clear();
15169  Utilities->CallLogPop(1059);
15170  return(false);
15171  }
15172  if(SetDataAndCheckLocations)
15173  {
15174  if(ForwardEntryPtr->LocationName == "")
15175  {
15176  SecondPassMessage(GiveMessages, "Error in timetable - location error in cross referenced trains " + MainHeadCode + " and " + OtherHeadCode +
15177  ". One or other service does not have a location set");
15178  TrainDataVector.clear();
15179  Utilities->CallLogPop(526);
15180  return(false);
15181  }
15182  if(ReverseEntryPtr->LocationName == "")
15183  {
15184  SecondPassMessage(GiveMessages, "Error in timetable - location error in cross referenced trains " + MainHeadCode + " and " + OtherHeadCode +
15185  ". One or other service does not have a location set");
15186  TrainDataVector.clear();
15187  Utilities->CallLogPop(527);
15188  return(false);
15189  }
15190  if(ForwardEntryPtr->LocationName != ReverseEntryPtr->LocationName)
15191  {
15192  SecondPassMessage(GiveMessages, "Error in timetable - cross referenced train " + OtherHeadCode +
15193  " is at a different location to the referencing train " + MainHeadCode);
15194  TrainDataVector.clear();
15195  Utilities->CallLogPop(842);
15196  return(false);
15197  }
15198  }
15199  // ignore shuttle repeat links for first time check
15200  if(!Shuttle)
15201  {
15202  if(ForwardEntryPtr->EventTime != ReverseEntryPtr->EventTime)
15203  {
15204  SecondPassMessage(GiveMessages, "Error in timetable - cross referenced train " + OtherHeadCode +
15205  " has a different event time to the referencing train " + MainHeadCode);
15206  TrainDataVector.clear();
15207  Utilities->CallLogPop(525);
15208  return(false);
15209  }
15210  }
15211  // need to allow for repeat times multiplying up by repeating time for shuttle repeat links
15212  // no need to check from reverse to forward as already checked links consistent, and if include will send message twice
15213  if(ForwardShuttleStart && ReverseShuttleFinish)
15214  // Shuttle must be true if these are true
15215  {
15216  if(!CheckShuttleRepeatTime(0, ForwardEntryPtr->EventTime, ReverseEntryPtr->EventTime, OtherTrainDataPtr->ActionVector.back().RearStartOrRepeatMins))
15217  {
15218  SecondPassMessage(GiveMessages, "Error in timetable - shuttle service " + MainHeadCode +
15219  " first repeat restart time not consistent with finish service " + OtherHeadCode);
15220  TrainDataVector.clear();
15221  Utilities->CallLogPop(1055);
15222  return(false);
15223  }
15224  }
15225  if((ReverseEntryPtr->Command == "Sfs") || (ReverseEntryPtr->Command == "Sns"))
15226  // doesn't matter about ForwardEntryPtr being Sfs/Sns as called for every occurrence of an 'OtherHeadCode' so won't escape
15227  {
15228  if(ReverseTDVectorNumber == ForwardTDVectorNumber)
15229  {
15230  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sfs' or 'Sns' event (" + OtherHeadCode +
15231  ") appears in the same sequence as the corresponding linked event " + MainHeadCode);
15232  TrainDataVector.clear();
15233  Utilities->CallLogPop(528);
15234  return(false);
15235  }
15236  }
15237  if(ReverseEntryPtr->Command == "Fjo")
15238  // doesn't matter about ForwardEntryPtr being Fjo as called for every occurrence of an 'OtherHeadCode' so won't escape
15239  {
15240  if(ReverseTDVectorNumber == ForwardTDVectorNumber)
15241  {
15242  SecondPassMessage(GiveMessages, "Error in timetable - an 'Fjo' event (" + OtherHeadCode +
15243  ") appears in the same sequence as the corresponding linked event " + MainHeadCode);
15244  TrainDataVector.clear();
15245  Utilities->CallLogPop(862);
15246  return(false);
15247  }
15248  }
15249  if(ReverseEntryPtr->Command == "Fns")
15250  // doesn't matter about ForwardEntryPtr being Fns as called for every occurrence of an 'OtherHeadCode' so won't escape
15251  {
15252  if(ReverseTDVectorNumber == ForwardTDVectorNumber)
15253  {
15254  SecondPassMessage(GiveMessages, "Error in timetable - an 'Fns' event (" + OtherHeadCode +
15255  ") appears in the same sequence as the corresponding linked event " + MainHeadCode);
15256  TrainDataVector.clear();
15257  Utilities->CallLogPop(529);
15258  return(false);
15259  }
15260  }
15261  if(ForwardEntryPtr->Command == "Sfs")
15262  {
15263  if((ReverseEntryPtr->Command != "fsp") && (ReverseEntryPtr->Command != "rsp"))
15264  {
15265  SecondPassMessage(GiveMessages,
15266  "Error in timetable - unable to find a corresponding split train event for the train that starts from a split whose headcode is " +
15267  MainHeadCode);
15268  TrainDataVector.clear();
15269  Utilities->CallLogPop(530);
15270  return(false);
15271  }
15272  }
15273  if((ForwardEntryPtr->Command == "fsp") || (ForwardEntryPtr->Command == "rsp"))
15274  {
15275  if(ReverseEntryPtr->Command != "Sfs")
15276  {
15277  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Sfs' event for the train split whose headcode is " +
15278  MainHeadCode);
15279  TrainDataVector.clear();
15280  Utilities->CallLogPop(839);
15281  return(false);
15282  }
15283  else
15284  {
15285  if(SetDataAndCheckLocations)
15286  {
15287  if(!(Track->TimetabledLocationNameAllocated(4, ForwardEntryPtr->LocationName)))
15288  {
15289  SecondPassMessage(GiveMessages, "Error in timetable - can't find timetabled location '" + ForwardEntryPtr->LocationName + "' in railway - perhaps there are concourses without platforms?");
15290  TrainDataVector.clear();
15291  Utilities->CallLogPop(849);
15292  return(false);
15293  }
15294  if(!(Track->OneNamedLocationElementAtLocation(0, ForwardEntryPtr->LocationName)))
15295  {
15296  SecondPassMessage(GiveMessages, "Error in timetable - can't find any named location elements at '" + ForwardEntryPtr->LocationName + "' - perhaps there are concourses without platforms?");
15297  TrainDataVector.clear();
15298  Utilities->CallLogPop(850);
15299  return(false);
15300  }
15301 //determine whether LocationName is a station or non-station
15302  bool StationLocation = false;
15303  for(TTrack::TTrackVectorIterator TEIt = Track->InactiveTrackVector.begin(); TEIt != Track->InactiveTrackVector.end(); TEIt++)
15304  {
15305  if(TEIt->LocationName == ForwardEntryPtr->LocationName)
15306  {
15307  if(TEIt->TrackType != NamedNonStationLocation)
15308  {
15309  StationLocation = true;
15310  }
15311  }
15312  }
15313  if(StationLocation)
15314  {
15315  if(!(Track->OneStationLongEnoughForSplit(0, ForwardEntryPtr->LocationName)))
15316  {
15317  SecondPassMessage(GiveMessages, "Error in timetable - location too short to split a train at " + ForwardEntryPtr->LocationName);
15318  TrainDataVector.clear();
15319  Utilities->CallLogPop(846);
15320  return(false);
15321  }
15322  }
15323  else
15324  {
15325  if(!(Track->OneNonStationLongEnoughForSplit(0, ForwardEntryPtr->LocationName)))
15326  {
15327  SecondPassMessage(GiveMessages, "Error in timetable - location too short to split a train at " + ForwardEntryPtr->LocationName);
15328  TrainDataVector.clear();
15329  Utilities->CallLogPop(2660);
15330  return(false);
15331  }
15332  }
15333  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
15334  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
15335  if(OtherTrainDataPtr->FixedDescription == "") //name changed at v2.16.1
15336  {
15337  OtherTrainDataPtr->FixedDescription = MainTrainDataPtr->FixedDescription;
15338  }
15339  }
15340  // NB: May not be set if main train is a service continuation without a description, if so can't do much about it but doesn't affect operation, just the train information display
15341  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed;
15342  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
15343  }
15344  }
15345  if(ForwardEntryPtr->Command == "Sns")
15346  {
15347  if(ReverseEntryPtr->Command != "Fns")
15348  {
15349  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Fns' event for the 'Sns' train whose headcode is " +
15350  MainHeadCode + " and is formed from a service with headcode " + OtherHeadCode);
15351  TrainDataVector.clear();
15352  Utilities->CallLogPop(531);
15353  return(false);
15354  }
15355  }
15356  if(ForwardEntryPtr->Command == "Fns")
15357  {
15358  if(ReverseEntryPtr->Command != "Sns")
15359  {
15360  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Sns' event for the train whose headcode is " + MainHeadCode +
15361  " and forms a new service with headcode " + OtherHeadCode);
15362  TrainDataVector.clear();
15363  Utilities->CallLogPop(840);
15364  return(false);
15365  }
15366  else
15367  {
15368  if(SetDataAndCheckLocations)
15369  {
15370  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
15371  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
15372  if(OtherTrainDataPtr->FixedDescription == "") //name changed at v2.16.1
15373  {
15374  OtherTrainDataPtr->FixedDescription = MainTrainDataPtr->FixedDescription;
15375  }
15376  }
15377  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
15378  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed;
15379  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
15380  }
15381  }
15382  if(ForwardEntryPtr->Command == "jbo")
15383  {
15384  if(ReverseEntryPtr->Command != "Fjo")
15385  {
15386  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Fjo' event for the train whose headcode is " + MainHeadCode +
15387  " and is joined by a train with headcode " + OtherHeadCode);
15388  TrainDataVector.clear();
15389  Utilities->CallLogPop(841);
15390  return(false);
15391  }
15392  }
15393  if(ForwardEntryPtr->Command == "Fjo")
15394  {
15395  if(ReverseEntryPtr->Command != "jbo")
15396  {
15397  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'jbo' event for the train whose headcode is " + MainHeadCode +
15398  " and joins a train with headcode " + OtherHeadCode);
15399  TrainDataVector.clear();
15400  Utilities->CallLogPop(532);
15401  return(false);
15402  }
15403  else
15404  {
15405  if(SetDataAndCheckLocations)
15406  {
15407  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
15408  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
15409  }
15410 /*
15411  if((MainTrainDataPtr->MaxRunningSpeed > 5) && (MainTrainDataPtr->MaxRunningSpeed < OtherTrainDataPtr->MaxRunningSpeed))
15412  {
15413  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed; //this should only be set when the trains join
15414  } not when internal timetable being compiled. Dropped at v2.15.0 when Brent Mackie reported error on 18/02/23 via discord
15415 
15416  //added test for > 5 [5 used instead of 0 because of possible floating point errors - though unlikely] above at v1.3.1 because the train will have a
15417  //zero MaxRunningSpeed if it continues from another service - its max speed is set when it takes over from the other service
15418  //notified of this problem by Ian Walker in his email of 25/03/13. Probably redundant anyway because the max speed is reduced at the changeover if the
15419  //'joined by' train's max speed is less.
15420 */
15421  }
15422  }
15423  if(ForwardShuttleStart)
15424  // (ForwardEntryPtr->Command == "Sns-sh") || (ForwardEntryPtr->Command == "Snt-sh"))
15425  {
15426  if(!ReverseShuttleFinish)
15427  // (ReverseEntryPtr->Command != "Fns-sh") && (ReverseEntryPtr->Command != "Frh-sh"))
15428  {
15429  SecondPassMessage(GiveMessages, "Error in timetable - incorrect shuttle link to train whose headcode is " + MainHeadCode +
15430  " from train whose headcode is " + OtherHeadCode + ", has to be Fns-sh, Frh-sh");
15431  TrainDataVector.clear();
15432  Utilities->CallLogPop(1056);
15433  return(false);
15434  }
15435  }
15436  if(ReverseShuttleStart)
15437  // (ReverseEntryPtr->Command == "Sns-sh") || (ReverseEntryPtr->Command == "Snt-sh"))
15438  {
15439  if(!ForwardShuttleFinish)
15440  // (ForwardEntryPtr->Command != "Fns-sh") && (ForwardEntryPtr->Command != "Frh-sh"))
15441  {
15442  SecondPassMessage(GiveMessages, "Error in timetable - incorrect shuttle link to train whose headcode is " + OtherHeadCode +
15443  " from train whose headcode is " + MainHeadCode + ", has to be Fns-sh, Frh-sh");
15444  TrainDataVector.clear();
15445  Utilities->CallLogPop(1057);
15446  return(false);
15447  }
15448  else
15449  {
15450  if(SetDataAndCheckLocations)
15451  {
15452  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
15453  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
15454  }
15455 /* don't need LinkedTrainEntryPtr for 'OtherTrain' & don't need data transfer as this is done in the
15456  non-repeating link for Sns-sh & is provided at the outset for Snt-sh
15457 */
15458  }
15459  }
15460  // check repeat information consistent if present
15461  // note that won't be affected by the non-repeating shuttle links as these are in NonRepeatingShuttleLinkHeadCode
15462  // and those not accessed here
15463 
15464  // still need to check the non-repeating links and that they have no repeats - do that outside this function
15465  bool MainRepeat = false, OtherRepeat = false;
15466  TActionVectorEntry MainRepeatEntry, OtherRepeatEntry;
15467 
15468  if(MainTrainDataPtr->ActionVector.at(MainTrainDataPtr->ActionVector.size() - 1).FormatType == Repeat)
15469  {
15470  MainRepeat = true;
15471  MainRepeatEntry = MainTrainDataPtr->ActionVector.at(MainTrainDataPtr->ActionVector.size() - 1);
15472  }
15473  if(OtherTrainDataPtr->ActionVector.at(OtherTrainDataPtr->ActionVector.size() - 1).FormatType == Repeat)
15474  {
15475  OtherRepeat = true;
15476  OtherRepeatEntry = OtherTrainDataPtr->ActionVector.at(OtherTrainDataPtr->ActionVector.size() - 1);
15477  }
15478  if((MainRepeat && !OtherRepeat) || (!MainRepeat && OtherRepeat))
15479  {
15480  SecondPassMessage(GiveMessages, "Error in timetable - only one repeat is provided for the train whose headcode is " + MainHeadCode +
15481  " and the associated train with headcode " + OtherHeadCode);
15482  TrainDataVector.clear();
15483  Utilities->CallLogPop(844);
15484  return(false);
15485  }
15486  if(MainRepeat && OtherRepeat)
15487  {
15488  if((MainRepeatEntry.EventTime != OtherRepeatEntry.EventTime) || (MainRepeatEntry.RearStartOrRepeatMins != OtherRepeatEntry.RearStartOrRepeatMins) ||
15489  (MainRepeatEntry.FrontStartOrRepeatDigits != OtherRepeatEntry.FrontStartOrRepeatDigits) ||
15490  (MainRepeatEntry.NumberOfRepeats != OtherRepeatEntry.NumberOfRepeats))
15491  {
15492  SecondPassMessage(GiveMessages, "Error in timetable - repeat items don't correspond for the train whose headcode is " + MainHeadCode +
15493  " and the associated train with headcode " + OtherHeadCode);
15494  TrainDataVector.clear();
15495  Utilities->CallLogPop(845);
15496  return(false);
15497  }
15498  }
15499  Utilities->CallLogPop(863);
15500  return(true);
15501 }
15502 
15503 // ---------------------------------------------------------------------------
15504 
15505 void TTrainController::StripSpaces(int Caller, AnsiString &Input)
15506 // strip both leading and trailing spaces at ends of text and spaces before and after all commas and semicolons within the text
15507 {
15508  // strip spaces from extreme ends of input
15509  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",StripSpaces," + AnsiString(Input));
15510  if(Input == "")
15511  {
15512  Utilities->CallLogPop(856);
15513  return;
15514  }
15515  while(Input[1] == ' ')
15516  {
15517  if(Input.Length() > 1)
15518  {
15519  Input = Input.SubString(2, Input.Length() - 1);
15520  }
15521  else
15522  {
15523  Input = "";
15524  Utilities->CallLogPop(857);
15525  return;
15526  }
15527  }
15528  if(Input == "")
15529  {
15530  Utilities->CallLogPop(858);
15531  return;
15532  }
15533  while(Input[Input.Length()] == ' ')
15534  {
15535  if(Input.Length() > 1)
15536  {
15537  Input = Input.SubString(1, Input.Length() - 1);
15538  }
15539  else
15540  {
15541  Input = "";
15542  Utilities->CallLogPop(859);
15543  return;
15544  }
15545  }
15546  // now strip spaces immediately after all commas and semicolons within the text
15547  AnsiString Output = "";
15548  bool DelimiterFound = false;
15549 
15550  for(int x = 1; x < Input.Length() + 1; x++)
15551  {
15552  if(DelimiterFound)
15553  {
15554  if(Input[x] == ' ')
15555  {
15556  continue;
15557  }
15558  }
15559  if((Input[x] != ',') && (Input[x] != ';'))
15560  {
15561  DelimiterFound = false;
15562  Output = Output + Input[x];
15563  }
15564  else
15565  {
15566  DelimiterFound = true;
15567  Output = Output + Input[x];
15568  }
15569  }
15570  if(Output == "")
15571  {
15572  Input = "";
15573  Utilities->CallLogPop(860);
15574  return;
15575  }
15576  // now strip spaces immediately before all commas and semicolons within the text
15577  Input = Output;
15578  Output = "";
15579  DelimiterFound = false;
15580  for(int x = Input.Length(); x > 0; x--)
15581  {
15582  if(DelimiterFound)
15583  {
15584  if(Input[x] == ' ')
15585  {
15586  continue;
15587  }
15588  }
15589  if((Input[x] != ',') && (Input[x] != ';'))
15590  {
15591  DelimiterFound = false;
15592  Output = AnsiString(Input[x]) + Output;
15593  }
15594  else
15595  {
15596  DelimiterFound = true;
15597  Output = AnsiString(Input[x]) + Output;
15598  }
15599  }
15600  Input = Output;
15601  Utilities->CallLogPop(861);
15602 }
15603 
15604 // ---------------------------------------------------------------------------
15605 
15606 bool TTrainController::IsSNTEntryLocated(int Caller, const TTrainDataEntry &TDEntry, AnsiString &LocationName) //this is the original version re-instated at v2.15.0 Beta6
15607 // checks if an Snt or Snt-sh entry with zero starting speed is followed (somewhere, not necessarily immediately) by a TimeLoc & has the same LocationName
15608 // and if so returns true. Also returns true for Snt, not Snt-sh, if at least 1 start element is a location & the entry is either
15609 // a signaller control entry & speed is zero or it is followed immediately by Frh, Fjo, Fns or F-nshs (allows empty stock pickup).
15610 // Always return false for entry at a continuation (may be named but not a stop location). Note that no successor validity checks
15611 // are done in this function, they must be done elsewhere.
15612 //a starting speed > 0 always returns false
15613 {
15614  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsSNTEntryLocated," + AnsiString(TDEntry.HeadCode));
15615  const TActionVectorEntry &AVEntry0 = TDEntry.ActionVector.at(0);
15616  LocationName = "";
15617  if(TDEntry.StartSpeed > 0)
15618  {
15619  Utilities->CallLogPop(1784);
15620  return(false);
15621  }
15622  if((AVEntry0.Command != "Snt") && (AVEntry0.Command != "Snt-sh"))
15623  {
15624  throw Exception("Error, first event not 'Snt' or 'Snt-sh' in IsSNTEntryLocated");
15625  }
15627  {
15628  Utilities->CallLogPop(852);
15629  return(false);
15630  }
15631  AnsiString LocRear = Track->TrackElementAt(507, AVEntry0.RearStartOrRepeatMins).ActiveTrackElementName;
15632  AnsiString LocFront = Track->TrackElementAt(508, AVEntry0.FrontStartOrRepeatDigits).ActiveTrackElementName;
15633 
15634  if(LocRear != "")
15635  {
15636  LocationName = LocRear;
15637  }
15638  else
15639  {
15640  LocationName = LocFront;
15641  }
15642  if(LocationName == "")
15643  {
15644  Utilities->CallLogPop(1036);
15645  return(false);
15646  }
15647  if(AVEntry0.SignallerControl)
15648  {
15649  Utilities->CallLogPop(1773);
15650  return(true);
15651  }
15652 // here if not a signaller start entry so must be at least one more entry, and it must be at the same location as the Snt to be located
15653 
15654 //Ok Not ok continue
15655 
15656 //Frh if Snt Frh-sh cdt
15657 //Fns if Snt Fns-sh fsp or rsp
15658 //Fjo if Snt TimeTimeLoc jbo
15659 //F-nshs if Snt pas dsc
15660 //TimeLoc dep Fer
15661 // TimeLoc arr
15662 
15663  for(unsigned int y = 0; y < TDEntry.ActionVector.size(); y++)
15664  {
15665  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(y);
15666  if(((AVEntry.Command == "Frh") || (AVEntry.Command == "Fjo") || (AVEntry.Command == "F-nshs") || (AVEntry.Command == "Fns")) && (AVEntry0.Command == "Snt")) // added Fjo at v2.0.0 for empty stock
15667  {
15668  Utilities->CallLogPop(1037);
15669  return(true);
15670  }
15671  if((AVEntry.FormatType == TimeLoc) && (AVEntry.LocationName == LocationName)) //will be a departure if same name- times not set yet so can't use them to confirm
15672  {
15673  Utilities->CallLogPop(2442);
15674  return(true);
15675  }
15676  if((AVEntry.FormatType == TimeLoc) && (AVEntry.LocationName != LocationName)) //arrival, not located
15677  {
15678  Utilities->CallLogPop(2438);
15679  return(false);
15680  }
15681  if((AVEntry.Command == "Fer") || (AVEntry.Command == "pas") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "Frh-sh") || (AVEntry.FormatType == TimeTimeLoc))
15682  {
15683  Utilities->CallLogPop(854);
15684  return(false);
15685  }
15686  if((AVEntry.Command == "cdt") || (AVEntry.Command == "dsc") || (AVEntry.Command == "fsp") || (AVEntry.Command == "rsp") || (AVEntry.Command == "jbo"))
15687  {
15688  continue;
15689  }
15690  }
15691  Utilities->CallLogPop(855);
15692  return(false);
15693 
15694 }
15695 
15696 // ---------------------------------------------------------------------------
15697 
15698 bool TTrainController::CheckStartPositionValidity(int Caller, AnsiString RearElementStr, AnsiString FrontElementStr, bool GiveMessages)
15699 {
15700  // checks that the new train start elements are valid - both exist & are connected, and that not
15701  // attempting to start on a diverging leg (i.e. one segment on points & other on element connected to diverging leg) <--dropped at v2.18.0
15702  // & not starting with front on a continuation
15703  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckStartPositionValidity," + RearElementStr + "," + FrontElementStr);
15704  int RearPosition = 0, FrontPosition = 0; // RearExitPos = 0; //dropped at v2.18.0
15705 
15706  RearPosition = Track->GetTrackVectorPositionFromString(5, RearElementStr, GiveMessages);
15707  if(RearPosition < 0)
15708  // error message given in GetTrackVectorPositionFromString
15709  {
15710  Utilities->CallLogPop(759);
15711  return(false);
15712  }
15713  FrontPosition = Track->GetTrackVectorPositionFromString(6, FrontElementStr, GiveMessages);
15714  if(FrontPosition < 0)
15715  // error message given in GetTrackVectorPositionFromString
15716  {
15717  Utilities->CallLogPop(760);
15718  return(false);
15719  }
15720  TTrackElement RearTrackElement = Track->TrackElementAt(490, RearPosition);
15721  TTrackElement FrontTrackElement = Track->TrackElementAt(491, FrontPosition);
15722 // TTrackType RearType = RearTrackElement.TrackType; //dropped at v2.18.0
15723  TTrackType FrontType = FrontTrackElement.TrackType;
15724 
15725  // check front & rear connected
15726  for(int x = 0; x < 4; x++)
15727  {
15728  if(RearTrackElement.Conn[x] == FrontPosition)
15729  {
15730 // RearExitPos = x; //dropped at v2.18.0
15731  break;
15732  }
15733  if(x == 3) //if it gets here & not already found then not connected
15734  {
15735  TimetableMessage(GiveMessages, "Front element: " + FrontTrackElement.ElementID + " not linked to rear element: " + RearTrackElement.ElementID);
15736  Utilities->CallLogPop(762);
15737  return(false);
15738  }
15739  }
15740  // check not starting with front on a continuation
15741  if(FrontType == Continuation)
15742  {
15743  TimetableMessage(GiveMessages, "Front of train attempting to start on a continuation at: " + FrontElementStr);
15744  Utilities->CallLogPop(937);
15745  return(false);
15746  }
15747  // check not starting on a level crossing
15748  if(Track->IsLCAtHV(43, FrontTrackElement.HLoc, FrontTrackElement.VLoc))
15749  {
15750  TimetableMessage(GiveMessages, "Train attempting to start on a level crossing at: " + FrontElementStr);
15751  Utilities->CallLogPop(1951);
15752  return(false);
15753  }
15754  if(Track->IsLCAtHV(44, RearTrackElement.HLoc, RearTrackElement.VLoc))
15755  {
15756  TimetableMessage(GiveMessages, "Train attempting to start on a level crossing at: " + RearElementStr);
15757  Utilities->CallLogPop(1952);
15758  return(false);
15759  }
15760  // check if trying to start on diverging leg of points - allowed at v2.18.0, checks done during operation
15761 /*
15762  if((RearType == Points) && (RearExitPos == 3))
15763  {
15764  TimetableMessage(GiveMessages, "Front of train attempting to start on element connected to diverging points at: " + RearElementStr);
15765  Utilities->CallLogPop(936);
15766  return(false);
15767  }
15768 
15769  if((FrontType == Points) && (RearTrackElement.ConnLinkPos[RearExitPos] == 3))
15770  {
15771  TimetableMessage(GiveMessages, "Rear of train attempting to start on element connected to diverging points at: " + FrontElementStr);
15772  Utilities->CallLogPop(1808);
15773  return(false);
15774  }
15775 */
15776  Utilities->CallLogPop(905);
15777  return(true);
15778 }
15779 
15780 // ---------------------------------------------------------------------------
15781 
15782 bool TTrainController::CheckStartAllowable(int Caller, int RearPosition, int RearExitPos, AnsiString HeadCode, bool ReportFlag, TActionEventType &EventType)
15783 // Rear & front element validity already checked in CheckStartPositionValidity
15784 // This checks for points in correct orientation, no train at start position and not starting on a locked route
15785 {
15786  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckStartAllowable," + AnsiString(RearPosition) + "," +
15787  AnsiString(RearExitPos));
15788  TTrackElement RearTrackElement = Track->TrackElementAt(517, RearPosition);
15789 
15790  if(RearTrackElement.TrackType == Continuation)
15791  {
15792  EventType = FailTrainEntry;
15793  }
15794  else
15795  {
15796  EventType = FailCreateTrain;
15797  }
15798  int FrontPosition = RearTrackElement.Conn[RearExitPos];
15799  TTrackElement FrontTrackElement = Track->TrackElementAt(798, FrontPosition);
15800  int FrontEntryPos = RearTrackElement.ConnLinkPos[RearExitPos];
15801  TTrackType RearType = RearTrackElement.TrackType;
15802  TTrackType FrontType = FrontTrackElement.TrackType;
15803  AnsiString RearName, FrontName;
15804 
15805  if(RearTrackElement.ActiveTrackElementName != "")
15806  {
15807  RearName = RearTrackElement.ActiveTrackElementName;
15808  }
15809  else
15810  {
15811  RearName = RearTrackElement.ElementID;
15812  }
15813  if(FrontTrackElement.ActiveTrackElementName != "")
15814  {
15815  FrontName = FrontTrackElement.ActiveTrackElementName;
15816  }
15817  else
15818  {
15819  FrontName = FrontTrackElement.ElementID;
15820  }
15821  TPrefDirElement PrefDirElement; // needed for next function but not used
15822  int LockedVectorNumber; // needed for next function but not used
15823 
15824  if(AllRoutes->IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber(12, FrontPosition, FrontEntryPos, PrefDirElement, LockedVectorNumber))
15825  {
15826  if(ReportFlag)
15827  {
15828  if(EventType == FailCreateTrain)
15829  {
15830  EventType = FailCreateLockedRoute;
15831  }
15832  else
15833  {
15834  EventType = FailEnterLockedRoute;
15835  }
15836  LogActionError(47, HeadCode, "", EventType, FrontName);
15837  }
15838  Utilities->CallLogPop(940);
15839  return(false);
15840  }
15841  if(AllRoutes->IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber(13, RearPosition, RearExitPos, PrefDirElement, LockedVectorNumber))
15842  {
15843  if(ReportFlag)
15844  {
15845  if(EventType == FailCreateTrain)
15846  {
15847  EventType = FailCreateLockedRoute;
15848  }
15849  else
15850  {
15851  EventType = FailEnterLockedRoute;
15852  }
15853  LogActionError(48, HeadCode, "", EventType, RearName);
15854  }
15855  Utilities->CallLogPop(1809);
15856  return(false);
15857  }
15858  if((RearType != Bridge) && (RearTrackElement.TrainIDOnElement > -1))
15859  {
15860  if(ReportFlag)
15861  {
15862  LogActionError(27, HeadCode, "", EventType, RearName);
15863  }
15864  Utilities->CallLogPop(1810);
15865  return(false);
15866  }
15867  if((FrontType != Bridge) && (FrontTrackElement.TrainIDOnElement > -1))
15868  {
15869  if(ReportFlag)
15870  {
15871  if(EventType == FailCreateTrain)
15872  {
15873  LogActionError(28, HeadCode, "", EventType, FrontName);
15874  }
15875  else
15876  {
15877  LogActionError(43, HeadCode, "", EventType, RearName);
15878  }
15879  }
15880  Utilities->CallLogPop(941);
15881  return(false);
15882  }
15883  if(RearType == Bridge)
15884  {
15885  if((RearExitPos > 1) && (RearTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 > -1))
15886  {
15887  if(ReportFlag)
15888  {
15889  LogActionError(29, HeadCode, "", EventType, RearName);
15890  }
15891  Utilities->CallLogPop(942);
15892  return(false);
15893  }
15894  if((RearExitPos < 2) && (RearTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 > -1))
15895  {
15896  if(ReportFlag)
15897  {
15898  LogActionError(30, HeadCode, "", EventType, RearName);
15899  }
15900  Utilities->CallLogPop(943);
15901  return(false);
15902  }
15903  }
15904  if(FrontType == Bridge)
15905  {
15906  if((FrontEntryPos > 1) && (FrontTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 > -1))
15907  {
15908  if(ReportFlag)
15909  {
15910  if(EventType == FailCreateTrain)
15911  {
15912  LogActionError(31, HeadCode, "", EventType, FrontName);
15913  }
15914  else
15915  {
15916  LogActionError(44, HeadCode, "", EventType, RearName);
15917  }
15918  }
15919  Utilities->CallLogPop(944);
15920  return(false);
15921  }
15922  if((FrontEntryPos < 2) && (FrontTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 > -1))
15923  {
15924  if(ReportFlag)
15925  {
15926  if(EventType == FailCreateTrain)
15927  {
15928  LogActionError(45, HeadCode, "", EventType, FrontName);
15929  }
15930  else
15931  {
15932  LogActionError(46, HeadCode, "", EventType, RearName);
15933  }
15934  }
15935  Utilities->CallLogPop(945);
15936  return(false);
15937  }
15938  }
15939  EventType = FailCreatePoints; //modified at v2.18.0 so only fails if starting positions conflict with point attribute
15940  if(RearType == Points)
15941  {
15942  if(((RearTrackElement.Attribute == 1) && (RearExitPos == 1)) || ((RearTrackElement.Attribute == 0) && (RearExitPos == 3)))
15943  {
15944  if(ReportFlag)
15945  {
15946  LogActionError(33, HeadCode, "", FailCreatePoints, RearName);
15947  StopTTClockMessage(157, HeadCode + " can't be created, points set wrongly at " + RearName);
15948  }
15949  Utilities->CallLogPop(933);
15950  return(false);
15951  }
15952  }
15953  if(FrontType == Points)
15954  {
15955  if(((FrontTrackElement.Attribute == 1) && (FrontEntryPos == 1)) || ((FrontTrackElement.Attribute == 0) && (FrontEntryPos == 3)))
15956  {
15957  if(ReportFlag)
15958  {
15959  LogActionError(34, HeadCode, "", FailCreatePoints, FrontName);
15960  StopTTClockMessage(158, HeadCode + " can't be created, points set wrongly at " + RearName);
15961  }
15962  Utilities->CallLogPop(934);
15963  return(false);
15964  }
15965  }
15966 
15967  //this section added at v2.9.1 to prevent entry for a train when there's a route set against it
15968  int HLoc = Track->TrackElementAt(1027, RearPosition).HLoc;
15969  int VLoc = Track->TrackElementAt(1028, RearPosition).VLoc;
15970  int ELink = Track->TrackElementAt(1029, RearPosition).Link[RearExitPos]; //if route entry corresponds to RearExitPos then it's set against the train
15971  int RouteNumber; //not used
15972  if(Track->TrackElementAt(1030, RearPosition).TrackType == Continuation)
15973  {
15974  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(8, HLoc, VLoc, ELink, RouteNumber))
15975  {
15976  EventType = FailEntryRouteSetAgainst;
15977  if(ReportFlag)
15978  {
15979  LogActionError(63, HeadCode, "", EventType, RearName);
15980  }
15981  Utilities->CallLogPop(2317);
15982  return(false);
15983  }
15984  }
15985  Utilities->CallLogPop(939);
15986  return(true);
15987 }
15988 
15989 // ---------------------------------------------------------------------------
15990 
15991 AnsiString TTrainController::GetRepeatHeadCode(int Caller, AnsiString BaseHeadCode, int RepeatNumber, int IncDigits)
15992 {
15993  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetRepeatHeadCode," + BaseHeadCode + "," + AnsiString(RepeatNumber) +
15994  "," + AnsiString(IncDigits));
15995  if(!Last2CharactersBothDigits(1, BaseHeadCode) && (IncDigits > 0))
15996  {
15997  throw Exception("Error, last 2 characters not both digits and IncDigits > 0 in GetRepeatHeadCode");
15998  }
15999  if(!Last2CharactersBothDigits(2, BaseHeadCode))
16000  {
16001  Utilities->CallLogPop(1893);
16002  return(BaseHeadCode);
16003  }
16004  int BaseDigits = BaseHeadCode.SubString(3, 2).ToInt();
16005  int NextRepeatDigits = BaseDigits + (IncDigits * RepeatNumber);
16006 
16007  while(NextRepeatDigits >= 100)
16008  {
16009  NextRepeatDigits -= 100; // rolls over after 99
16010  }
16011  AnsiString NextRepeatDigitsStr = AnsiString(NextRepeatDigits);
16012 
16013  if(NextRepeatDigitsStr.Length() < 2)
16014  {
16015  NextRepeatDigitsStr = AnsiString('0') + NextRepeatDigitsStr;
16016  }
16017  AnsiString NextRepeatHeadCode = BaseHeadCode.SubString(1, 2) + NextRepeatDigitsStr;
16018 
16019  Utilities->CallLogPop(1365);
16020  return(NextRepeatHeadCode);
16021 }
16022 
16023 // ---------------------------------------------------------------------------
16024 
16025 TDateTime TTrainController::GetRepeatTime(int Caller, TDateTime BasicTime, int RepeatNumber, int IncMinutes)
16026 {
16027  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetRepeatTime," + AnsiString(double(BasicTime)) + "," +
16028  AnsiString(RepeatNumber) + "," + AnsiString(IncMinutes));
16029  TDateTime NextRepeatTime = BasicTime + TDateTime(((double)(RepeatNumber * IncMinutes)) / 1440.0); // 1440 = no. of minutes in 24h
16030  Utilities->CallLogPop(1366);
16031  return(NextRepeatTime);
16032 }
16033 
16034 // ---------------------------------------------------------------------------
16035 
16036 bool TTrainController::CheckShuttleRepeatTime(int Caller, TDateTime ForwardEventTime, TDateTime ReverseEventTime, int RepeatMinutes)
16037 // For success the ForwardEventTime + repeat time should == ReverseEventTime (allow 10secs either way since converting to doubles)
16038 {
16039  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckShuttleRepeatTime," + AnsiString(double(ForwardEventTime)) + "," +
16040  AnsiString(double(ReverseEventTime)) + "," + AnsiString(RepeatMinutes));
16041  int ForwardSecs = int(double(ForwardEventTime) * 86400);
16042  int ReverseSecs = int(double(ReverseEventTime) * 86400);
16043  int RepeatSecs = RepeatMinutes * 60;
16044 
16045  if((ForwardSecs > (ReverseSecs - RepeatSecs + 10)) || (ForwardSecs < (ReverseSecs - RepeatSecs - 10)))
16046  {
16047  Utilities->CallLogPop(1367);
16048  return(false);
16049  }
16050  else
16051  {
16052  Utilities->CallLogPop(1368);
16053  return(true);
16054  }
16055 }
16056 
16057 // ---------------------------------------------------------------------------
16058 
16059 bool TTrainController::CheckNonRepeatingShuttleLinksAndSetData(int Caller, AnsiString MainHeadCode, AnsiString NonRepeatingHeadCode, bool SetDataAndCheckLocations, bool GiveMessages)
16060 // check for proper non-repeating link cross references and that they have no repeats & that times are consistent, set links if SetDataAndCheckLocations true
16061 
16062 /* Double crosslink (shuttle) table:
16063 Command Format OtherHead NonRepeating- LinkTrain- NonRepeating- Decsription
16064  Code ShuttleLink- EntryPtr ShuttleLink-
16065  HeadCode EntryPtr
16066 
16067 Snt-sh SNTShuttle Y (rtn shuttle) N Y (rtn sh) N Simple shuttle - no feeder service
16068 Frh-sh TimeCmdHeadCode Y (outwd shuttle) N Y (outwd sh) N Simple shuttle - no finishing service
16069 F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
16070 Sns-sh SNSShuttle Y (rtn shuttle) Y (feeder) Y (rtn) Y (fdr) Shuttle link from feeder service
16071 Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
16072 Fns-sh FSHNewService Y (outwd shuttle) Y (finishing) Y (outwd sh) Y (finish) Shuttle link to finishing service
16073 
16074 Note: Any shuttle start can have any finish - feeder and finish, neither, feeder but no finish & vice versa.
16075 */
16076 
16077 {
16078  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckNonRepeatingShuttleLinksAndSetData," + MainHeadCode + "," +
16079  NonRepeatingHeadCode);
16080  int ForwardCount = 0;
16081  int ReverseCount = 0;
16082  unsigned int ForwardTDVectorNumber, ReverseTDVectorNumber;
16083  TActionVectorEntry *ReverseEntryPtr = 0, *ForwardEntryPtr = 0;
16084  // Forward corresponds to Main, Reverse to Other
16085  TTrainDataEntry *MainTrainDataPtr = 0;
16086  TTrainDataEntry *OtherTrainDataPtr = 0;
16087 
16088  // forward check
16089  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
16090  {
16091  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
16092  if(TDEntry.HeadCode == MainHeadCode)
16093  {
16094  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
16095  {
16096  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
16097  if(AVEntry.NonRepeatingShuttleLinkHeadCode == NonRepeatingHeadCode)
16098  {
16099  MainTrainDataPtr = &TrainDataVector.at(x);
16100  ForwardEntryPtr = &AVEntry;
16101  ForwardCount++;
16102  ForwardTDVectorNumber = x;
16103  }
16104  }
16105  }
16106  }
16107  if(ForwardCount == 0)
16108  // this is an exception because the headcodes are selected in the same order as the forward check
16109  {
16110  throw Exception("Error, ForwardCount == 0 in CheckNonRepeatingShuttleLinksAndSetData after called with found values");
16111  }
16112  if(ForwardCount > 1)
16113  {
16114  SecondPassMessage(GiveMessages, "Error in timetable - found more than one reference to " + NonRepeatingHeadCode + " from a train whose headcode is " +
16115  MainHeadCode);
16116  TrainDataVector.clear();
16117  Utilities->CallLogPop(1061);
16118  return(false);
16119  }
16120  // reverse check
16121  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
16122  {
16123  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
16124  if(TDEntry.HeadCode == NonRepeatingHeadCode)
16125  {
16126  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
16127  {
16128  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
16129  if(AVEntry.NonRepeatingShuttleLinkHeadCode == MainHeadCode)
16130  {
16131  OtherTrainDataPtr = &TrainDataVector.at(x);
16132  ReverseCount++;
16133  ReverseEntryPtr = &AVEntry;
16134  ReverseTDVectorNumber = x;
16135  }
16136  }
16137  }
16138  }
16139 
16140  if(ReverseCount == 0)
16141  {
16142  SecondPassMessage(GiveMessages, "Error in timetable - cross reference missing in either " + MainHeadCode + " or " + NonRepeatingHeadCode);
16143  TrainDataVector.clear();
16144  Utilities->CallLogPop(1062);
16145  return(false);
16146  }
16147  if(ReverseCount > 1)
16148  {
16149  SecondPassMessage(GiveMessages, "Error in timetable - found more than one reference to " + MainHeadCode + " from a train whose headcode is " +
16150  NonRepeatingHeadCode);
16151  TrainDataVector.clear();
16152  Utilities->CallLogPop(1063);
16153  return(false);
16154  }
16155  if(((ForwardEntryPtr->Command == "F-nshs") || (ForwardEntryPtr->Command == "Sns-fsh")) && (MainTrainDataPtr->ActionVector.back().FormatType == Repeat))
16156  {
16157  SecondPassMessage(GiveMessages, "Error in timetable - shuttle connecting train " + MainHeadCode + " shouldn't have a repeat");
16158  TrainDataVector.clear();
16159  Utilities->CallLogPop(1064);
16160  return(false);
16161  }
16162  if((ForwardEntryPtr->Command != "F-nshs") && (ForwardEntryPtr->Command != "Sns-fsh") && (MainTrainDataPtr->ActionVector.back().FormatType != Repeat))
16163  {
16164  SecondPassMessage(GiveMessages, "Error in timetable - shuttle train " + MainHeadCode + " does not have a repeat item");
16165  TrainDataVector.clear();
16166  Utilities->CallLogPop(1065);
16167  return(false);
16168  }
16169  if(SetDataAndCheckLocations)
16170  {
16171  if(ForwardEntryPtr->LocationName == "")
16172  {
16173  SecondPassMessage(GiveMessages, "Error in timetable - location error in cross referenced trains " + MainHeadCode + " and " + NonRepeatingHeadCode +
16174  ". One or other service does not have a location set");
16175  TrainDataVector.clear();
16176  Utilities->CallLogPop(1066);
16177  return(false);
16178  }
16179  if(ReverseEntryPtr->LocationName == "")
16180  {
16181  SecondPassMessage(GiveMessages, "Error in timetable - location error in cross referenced trains " + MainHeadCode + " and " + NonRepeatingHeadCode +
16182  ". One or other service does not have a location set");
16183  TrainDataVector.clear();
16184  Utilities->CallLogPop(1067);
16185  return(false);
16186  }
16187  if(ForwardEntryPtr->LocationName != ReverseEntryPtr->LocationName)
16188  {
16189  SecondPassMessage(GiveMessages, "Error in timetable - cross referenced train " + NonRepeatingHeadCode +
16190  " is at a different location to the referencing train " + MainHeadCode);
16191  TrainDataVector.clear();
16192  Utilities->CallLogPop(1068);
16193  return(false);
16194  }
16195  }
16196  if(ForwardEntryPtr->Command == "F-nshs")
16197  // i.e. the non repeating link into the shuttle service
16198  {
16199  if(ForwardEntryPtr->EventTime != ReverseEntryPtr->EventTime)
16200  {
16201  SecondPassMessage(GiveMessages, "Error in timetable - shuttle in-link service " + MainHeadCode +
16202  " finish time not consistent with start time of shuttle service " + NonRepeatingHeadCode);
16203  TrainDataVector.clear();
16204  Utilities->CallLogPop(1069);
16205  return(false);
16206  }
16207  }
16208  if(ForwardEntryPtr->Command == "Fns-sh")
16209  // i.e. the non repeating link out from the shuttle service
16210  {
16211  if(!CheckNonRepeatingShuttleLinkTime(0, ForwardEntryPtr->EventTime, ReverseEntryPtr->EventTime,
16212  MainTrainDataPtr->ActionVector.back().RearStartOrRepeatMins, MainTrainDataPtr->ActionVector.back().NumberOfRepeats))
16213  {
16214  SecondPassMessage(GiveMessages, "Error in timetable - service " + NonRepeatingHeadCode + ", which links out from shuttle service " + MainHeadCode +
16215  ", has the wrong start time. It should correspond to the finish time of the last shuttle.");
16216  TrainDataVector.clear();
16217  Utilities->CallLogPop(1070);
16218  return(false);
16219  }
16220  }
16221  if((ForwardEntryPtr->Command == "F-nshs") || (ForwardEntryPtr->Command == "Sns-fsh"))
16222  // i.e. a non repeating link to or from the shuttle service
16223  {
16224  if(ReverseTDVectorNumber == ForwardTDVectorNumber)
16225  {
16226  SecondPassMessage(GiveMessages, "Error in timetable - the non repeating link service " + NonRepeatingHeadCode +
16227  " appears in the same sequence as the corresponding shuttle service " + MainHeadCode);
16228  TrainDataVector.clear();
16229  Utilities->CallLogPop(1071);
16230  return(false);
16231  }
16232  }
16233 /* it's allowed to have a different description
16234  if((ForwardEntryPtr->Command == "F-nshs") || (ForwardEntryPtr->Command == "Sns-fsh"))//i.e. a non repeating link to or from the shuttle service
16235  {
16236  if((MainTrainDataPtr->Description != "") && (OtherTrainDataPtr->Description != "") && (MainTrainDataPtr->Description != OtherTrainDataPtr->Description))
16237  {
16238  SecondPassMessage(GiveMessages, "Error in timetable - the non repeating link service " + NonRepeatingHeadCode + " has a different description to the corresponding shuttle service " + MainHeadCode);
16239  TrainDataVector.clear();
16240  Utilities->CallLogPop(1072);
16241  return false;
16242  }
16243  }
16244 */
16245  if(ForwardEntryPtr->Command == "Sns-sh")
16246  {
16247  if(ReverseEntryPtr->Command != "F-nshs")
16248  {
16249  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'F-nshs' event for the 'Sns-sh' train whose headcode is " +
16250  MainHeadCode + " and is a new shuttle service formed from the service with headcode " + NonRepeatingHeadCode);
16251  TrainDataVector.clear();
16252  Utilities->CallLogPop(1073);
16253  return(false);
16254  }
16255  }
16256  if(ForwardEntryPtr->Command == "F-nshs")
16257  {
16258  if(ReverseEntryPtr->Command != "Sns-sh")
16259  {
16260  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Sns-sh' event for the 'F-nshs' train whose headcode is " +
16261  MainHeadCode + " and forms a new shuttle service with headcode " + NonRepeatingHeadCode);
16262  TrainDataVector.clear();
16263  Utilities->CallLogPop(1074);
16264  return(false);
16265  }
16266  else
16267  {
16268  if(SetDataAndCheckLocations)
16269  {
16270  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
16271  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
16272  if(OtherTrainDataPtr->FixedDescription == "") //name changed at v2.16.1
16273  {
16274  OtherTrainDataPtr->FixedDescription = MainTrainDataPtr->FixedDescription;
16275  }
16276  }
16277  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
16278  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed;
16279  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
16280  }
16281  }
16282  if(ForwardEntryPtr->Command == "Sns-fsh")
16283  {
16284  if(ReverseEntryPtr->Command != "Fns-sh")
16285  {
16286  SecondPassMessage(GiveMessages,
16287  "Error in timetable - unable to find a corresponding 'Fns-sh' event for the 'Sns-fsh' non-shuttle service whose headcode is " + MainHeadCode +
16288  " formed from a shuttle service with headcode " + NonRepeatingHeadCode);
16289  TrainDataVector.clear();
16290  Utilities->CallLogPop(1075);
16291  return(false);
16292  }
16293  }
16294  if(ForwardEntryPtr->Command == "Fns-sh")
16295  {
16296  if(ReverseEntryPtr->Command != "Sns-fsh")
16297  {
16298  SecondPassMessage(GiveMessages,
16299  "Error in timetable - unable to find a corresponding 'Sns-fsh' event for the 'Fns-sh' shuttle service whose headcode is " + MainHeadCode +
16300  " and forms a new non-shuttle service with headcode " + NonRepeatingHeadCode);
16301  TrainDataVector.clear();
16302  Utilities->CallLogPop(1076);
16303  return(false);
16304  }
16305  else
16306  {
16307  if(SetDataAndCheckLocations)
16308  {
16309  ForwardEntryPtr->NonRepeatingShuttleLinkEntryPtr = OtherTrainDataPtr;
16310  // links to the non-repeating non-shuttle linked service
16311  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
16312  // needed for creating formatted timetable
16313  if(OtherTrainDataPtr->FixedDescription == "") //name changed at v2.16.1
16314  {
16315  OtherTrainDataPtr->FixedDescription = MainTrainDataPtr->FixedDescription;
16316  }
16317  }
16318  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
16319  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed;
16320  }
16321  }
16322  Utilities->CallLogPop(1077);
16323  return(true);
16324 }
16325 
16326 // ---------------------------------------------------------------------------
16327 
16328 bool TTrainController::CheckNonRepeatingShuttleLinkTime(int Caller, TDateTime ForwardEventTime, TDateTime ReverseEventTime, int RepeatMinutes, int RepeatNumber)
16329 // Forward train is the finish shuttle entry 'Fns-sh'.
16330 // The Reverse (new non-repeating service) time must == Forward time + (RepeatMins * RepeatNumber) but allow 10 secs either side
16331 {
16332  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckNonRepeatingShuttleLinkTime," + AnsiString(double(ForwardEventTime))
16333  + "," + AnsiString(double(ReverseEventTime)) + "," + AnsiString(RepeatMinutes) + "," + AnsiString(RepeatNumber));
16334  int ForwardSecs = int(double(ForwardEventTime) * 86400);
16335  int ReverseSecs = int(double(ReverseEventTime) * 86400);
16336  int RepeatSecs = RepeatMinutes * RepeatNumber * 60;
16337 
16338  if((ReverseSecs > (ForwardSecs + RepeatSecs + 10)) || (ReverseSecs < (ForwardSecs + RepeatSecs - 10)))
16339  {
16340  Utilities->CallLogPop(1369);
16341  return(false);
16342  }
16343  else
16344  {
16345  Utilities->CallLogPop(1370);
16346  return(true);
16347  }
16348 }
16349 
16350 // ---------------------------------------------------------------------------
16351 
16352 bool TTrainController::CheckShuttleServiceIntegrity(int Caller, TTrainDataEntry *TDEntryPtr, bool GiveMessages)
16353 // check that each shuttle start ends either in Fns or Fxx-sh (though a single service can't end in Fxx-sh), and that
16354 // when the Fxx-sh is reached it references the original start and not another shuttle - not allowed to link two shuttles,
16355 // don't ever need to and as designed would skip repeats.
16356 
16357 // enter with TDEntry a shuttle start - Snt-sh or Sns-sh
16358 {
16359  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckShuttleServiceIntegrity," + AnsiString(TDEntryPtr->HeadCode));
16360  if(TDEntryPtr->ActionVector.back().FormatType != Repeat)
16361  {
16362  throw Exception("Error - last entry in " + TDEntryPtr->HeadCode + " service is not a repeat - should have already found this error");
16363  }
16364  TTrainDataEntry *ShuttleStartAddress = TDEntryPtr;
16365  AnsiString OriginalHeadCode = TDEntryPtr->HeadCode;
16366  AnsiString LastActionCommand = (TDEntryPtr->ActionVector.end() - 2)->Command;
16367 
16368  if((LastActionCommand != "Fns") && (LastActionCommand != "Fns-sh") && (LastActionCommand != "Frh-sh"))
16369  {
16370  SecondPassMessage(GiveMessages, "Error in timetable - last event in shuttle service " + TDEntryPtr->HeadCode + " is not 'Fns', 'Fns-sh' or 'Frh-sh'");
16371  TrainDataVector.clear();
16372  Utilities->CallLogPop(1091);
16373  return(false);
16374  }
16375  while(LastActionCommand == "Fns")
16376  {
16377  TDEntryPtr = (TDEntryPtr->ActionVector.end() - 2)->LinkedTrainEntryPtr;
16378  LastActionCommand = (TDEntryPtr->ActionVector.end() - 2)->Command;
16379  if((LastActionCommand != "Fns") && (LastActionCommand != "Fns-sh") && (LastActionCommand != "Frh-sh"))
16380  {
16381  SecondPassMessage(GiveMessages,
16382  "Error in timetable - last event in a continuation shuttle service (i.e links back to a shuttle) whose headcode is " + TDEntryPtr->HeadCode +
16383  " is not 'Fns', 'Fns-sh' or 'Frh-sh'");
16384  TrainDataVector.clear();
16385  Utilities->CallLogPop(1092);
16386  return(false);
16387  }
16388  }
16389  // exit the 'while' with LastActionCommand FSH-XX
16390  if((TDEntryPtr->ActionVector.end() - 2)->LinkedTrainEntryPtr != ShuttleStartAddress)
16391  {
16392  SecondPassMessage(GiveMessages, "Error in timetable - the event that ends service " + TDEntryPtr->HeadCode +
16393  " is a shuttle finish, but it doesn't link back to the start of the original shuttle starting service " + OriginalHeadCode +
16394  ". The linking of two or more shuttles is not permitted.");
16395  TrainDataVector.clear();
16396  Utilities->CallLogPop(1093);
16397  return(false);
16398  }
16399  Utilities->CallLogPop(1094);
16400  return(true);
16401 }
16402 
16403 // ---------------------------------------------------------------------------
16404 
16405 void TTrainController::TimetableMessage(bool GiveMessages, AnsiString Message)
16406 {
16407  if(!GiveMessages)
16408  {
16409  return;
16410  }
16411  // if(ServiceReference == "") ShowMessage(Message);
16412  if(!CheckHeadCodeValidity(12, false, ServiceReference))
16413  {
16414  ShowMessage(ServiceReference + " (not a valid service ref.): " + Message); //amended at v2.15.1 to give information on 'service' so can find it in lh list
16415  }
16416  // changed from above at v2.3.0 as a meaningless value for 'Timetable invalid - unable to find a valid start time on its own line' (uses last entry text)
16417  // false means don't give messages within the function
16418  else
16419  {
16420  ShowMessage("Service " + ServiceReference + ": " + Message);
16421  }
16422 }
16423 
16424 // ---------------------------------------------------------------------------
16425 
16426 void TTrainController::SecondPassMessage(bool GiveMessages, AnsiString Message)
16427 {
16428  if(!GiveMessages)
16429  {
16430  return;
16431  }
16432  ShowMessage(Message);
16433 }
16434 
16435 // ---------------------------------------------------------------------------
16436 
16437 AnsiString TTrainController::MinsToAnsiTime(int Input) //added at v2.15.0
16438 {
16439  TrainController->LogEvent("MinsToAnsiTime");
16440  Utilities->CallLog.push_back(Utilities->TimeStamp() + ",MinsToAnsiTime," + Input);
16441  int Mins = Input, Hrs = 0;
16442  while(Mins > 59)
16443  {
16444  Mins -= 60;
16445  Hrs++;
16446  }
16447  AnsiString AnsiMins = AnsiString(Mins);
16448  if(AnsiMins.Length() == 1)
16449  {
16450  AnsiMins = "0" + AnsiMins;
16451  }
16452  AnsiString AnsiHrs = AnsiString(Hrs);
16453  if(AnsiHrs.Length() == 1)
16454  {
16455  AnsiHrs = "0" + AnsiHrs;
16456  }
16457  Utilities->CallLogPop(2577);
16458  return(AnsiHrs + ':' + AnsiMins);
16459 }
16460 
16461 // $$$$$$$$$$$$$$$$$$$$$$$ End of Timetable Functions $$$$$$$$$$$$$$$$$$$$$$$
16462 // ---------------------------------------------------------------------------
16463 
16464 void TTrainController::LogActionError(int Caller, AnsiString HeadCode, AnsiString OtherHeadCode, TActionEventType ActionEventType, AnsiString LocationID)
16465 // FailTrainEntry: 06:00:10 HELD: 2F43 can't enter railway, train obstructing entry position 57-N5
16466 // FailCreateTrain: 06:00:10 HELD: 2F43 can't be created, train obstructing start position 57-N5
16467 // FailCreateLockedRoute: 06:00:10 HELD: 2F43 can't be created on a locked route - start position 57-N5
16468 // FailEnterLockedRoute: 06:00:10 HELD: 2F43 can't enter on a locked route - start position 57-N5
16469 // FailCreatePoints: 06:00:10 HELD: 2F43 can't be created, points set against start position 57-N5
16470 // FailUnexpectedExitRailway: 06:00:10 ERROR: 2F43 left railway unexpectedly at position 57-N5
16471 // FailIncorrectExit: 06:00:10 ERROR: 2F43 left railway at an incorrect exit at position 57-N5
16472 // FailSPAD: 06:00:10 ERROR: 2F43 PASSED SIGNAL AT DANGER at position 57-N5
16473 // FailLockedRoute: 06:00:10 ERROR: SPAD Risk! Signals reset ahead of train, at position 57-N5
16474 // FailLocTooShort: 06:00:10 ERROR: 2F43 failed to split - location too short at Essex Road
16475 // FailSplitDueToOtherTrain: 06:00:10 HELD: 2F43 unable to split - another train is obstructing at Essex Road, please move it if possible
16476 // FailCrashed: 06:00:10: ERROR: 2F43 CRASHED INTO 3F43 at position 46-N7
16477 // FailDerailed: 06:00:10: ERROR: 2F43 DERAILED at position 46-N7
16478 // FailUnexpectedBuffers: 06:00:10: ERROR: 2F43 stopped at buffers unexpectedly at position 46-N7
16479 // FailMissedArrival: 06:00:10: ERROR: 2F43 failed to stop at Essex Road;
16480 // FailMissedSplit: 06:00:10: ERROR: 2F43 failed to split at Essex Road
16481 // FailMissedJBO: 06:00:10: ERROR: 2F43 failed to be joined by join other train at Essex Road
16482 // FailMissedDSC: 06:00:10: ERROR: 2F43 failed to change its description at Essex Road
16483 // FailMissedJoinOther: 06:00:10: ERROR: 2F43 failed to join other train at Essex Road
16484 // FailMissedTerminate: 06:00:10: ERROR: 2F43 failed to terminate at Essex Road
16485 // FailMissedNewService: 06:00:10: ERROR: 2F43 failed to form new service at Essex Road
16486 // FailMissedExitRailway: 06:00:10: ERROR: 2F43 failed to exit railway
16487 // FailMissedChangeDirection: 06:00:10: ERROR: 2F43 failed to change direction at Essex Road
16488 // FailMissedPass: 06:00:10: ERROR: 2F43 failed to pass Essex Road
16489 // FailBuffersPreventingStart: 06:00:10: ERROR: 2F43 facing buffers and unable to start at Essex Road
16490 // FailBufferCrash: 06:00:10: ERROR: 2F43 CRASHED INTO BUFFERS at 46-N7
16491 // FailLevelCrossingCrash: 06:00:10: ERROR: 2F43 CRASHED INTO ROAD TRAFFIC AT A LEVEL CROSSING at 46-N7
16492 // RouteForceCancelled: 06:00:10: ERROR: 2F43 forced a route cancellation by occupying it incorrectly at 46-N7
16493 // WaitingForJBO: 06:00:10: WARNING: 2F43 waiting to join 3F43 at Essex Road
16494 // WaitingForFJO: 06:00:10: WARNING: 2F43 waiting to be joined by 3F43 at Essex Road
16495 // FailEntryRouteSetAgainst: 06:00:10: WARNING: 2F43 can't enter railway, route set against it at entry position 57-N5 //added at v2.9.1
16496 // FailNoPowerUnableToDepart: 06:00:10: WARNING: 2F43 is without power so it can't depart from Essex Road // added at v2.19.1
16497 // FailTrainInFront 06:00:10: WARNING: 2F43 can't depart because there is a train in front at Essex Road // added at v2.19.1
16498 
16499 {
16500  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LogActionError," + HeadCode + "," + OtherHeadCode + "," +
16501  AnsiString(ActionEventType) + "," + LocationID);
16502  AnsiString BaseLog = "", Prefix = "", ErrorLog = "", WarningStr = "";
16503 
16504  TDateTime ActualTime = TrainController->TTClockTime; //moved from lower down at v2.9.1
16505  AnsiString TimeAndHeadCode = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode; //added at v2.9.1 to give more info to user
16506 
16507  Prefix = " ERROR: ";
16508  if(ActionEventType == FailTrainEntry)
16509  {
16510  Prefix = " HELD: ";
16511  ErrorLog = " can't enter railway, train obstructing entry position ";
16512  WarningStr = " can't enter railway, train obstructing entry position ";
16513  Display->WarningLog(1, TimeAndHeadCode + WarningStr + LocationID);
16514  }
16515  else if(ActionEventType == FailEntryRouteSetAgainst) //added at v2.9.1
16516  {
16517  Prefix = " HELD: ";
16518  ErrorLog = " can't enter railway, route set against it at entry position ";
16519  WarningStr = " can't enter railway, route set against it at entry position ";
16520  Display->WarningLog(10, TimeAndHeadCode + WarningStr + LocationID);
16521  }
16522  else if(ActionEventType == FailCreateTrain)
16523  {
16524  Prefix = " HELD: ";
16525  ErrorLog = " can't be created, train obstructing at ";
16526  WarningStr = " can't be created, train obstructing at ";
16527  Display->WarningLog(2, TimeAndHeadCode + WarningStr + LocationID);
16528  }
16529  else if(ActionEventType == FailCreateLockedRoute)
16530  {
16531  Prefix = " HELD: ";
16532  ErrorLog = " can't be created on a locked route at ";
16533  WarningStr = " can't be created on a locked route at ";
16534  Display->WarningLog(4, TimeAndHeadCode + WarningStr + LocationID);
16535  }
16536  else if(ActionEventType == FailEnterLockedRoute)
16537  {
16538  Prefix = " HELD: ";
16539  ErrorLog = " can't enter on a locked route at ";
16540  WarningStr = " can't enter on a locked route at ";
16541  Display->WarningLog(5, TimeAndHeadCode + WarningStr + LocationID);
16542  }
16543  else if(ActionEventType == FailCreatePoints)
16544  {
16545  Prefix = " HELD: ";
16546  ErrorLog = " can't be created, points set wrongly at ";
16547  WarningStr = " can't be created, points set wrongly at ";
16548  Display->WarningLog(3, TimeAndHeadCode + WarningStr + LocationID);
16549  }
16550  else if(ActionEventType == FailUnexpectedExitRailway)
16551  {
16552  ErrorLog = " left railway unexpectedly at ";
16553  UnexpectedExits++;
16554  }
16555  else if(ActionEventType == FailIncorrectExit)
16556  {
16557  ErrorLog = " left railway at an incorrect exit at ";
16558  IncorrectExits++;
16559  }
16560  else if(ActionEventType == FailLocTooShort)
16561  {
16562  ErrorLog = " failed to split - location too short at ";
16563  WarningStr = " failed to split, location too short at ";
16564  Display->WarningLog(6, TimeAndHeadCode + WarningStr + LocationID);
16565  }
16566  else if(ActionEventType == FailSplitDueToOtherTrain)
16567  {
16568  Prefix = " HELD: ";
16569  ErrorLog = " unable to split - other train obstructing at ";
16570  WarningStr = " unable to split - other train obstructing at ";
16571  Display->WarningLog(7, TimeAndHeadCode + WarningStr + LocationID);
16572  }
16573  else if(ActionEventType == FailUnexpectedBuffers)
16574  {
16575  ErrorLog = " stopped at buffers unexpectedly at position ";
16576  }
16577  else if(ActionEventType == FailMissedArrival)
16578  {
16579  ErrorLog = " failed to stop at ";
16580  MissedStops++;
16581  }
16582  else if(ActionEventType == FailMissedSplit)
16583  {
16584  ErrorLog = " failed to split at ";
16586  }
16587  else if(ActionEventType == FailMissedJBO)
16588  {
16589  ErrorLog = " failed to be joined by other train at ";
16591  }
16592  else if(ActionEventType == FailMissedDSC) //new at v2.15.0
16593  {
16594  ErrorLog = " failed to change its description at ";
16595 // OtherMissedEvents++; shouldn't count
16596  }
16597  else if(ActionEventType == FailMissedJoinOther)
16598  {
16599  ErrorLog = " failed to join other train at ";
16601  }
16602  else if(ActionEventType == FailMissedTerminate)
16603  {
16604  ErrorLog = " failed to terminate at ";
16606  }
16607  else if(ActionEventType == FailMissedNewService)
16608  {
16609  ErrorLog = " failed to form new service at ";
16611  }
16612  else if(ActionEventType == FailMissedExitRailway)
16613  {
16614  ErrorLog = " failed to exit railway ";
16616  }
16617  else if(ActionEventType == FailMissedChangeDirection)
16618  {
16619  ErrorLog = " failed to change direction at ";
16620 // OtherMissedEvents++; //dropped at v2.12.0 as cdt shouldn't count
16621  }
16622  else if(ActionEventType == FailMissedPass)
16623  {
16624  ErrorLog = " failed to pass ";
16625 // OtherMissedEvents++; //dropped at v2.12.0 as missed pass shouldn't count
16626  }
16627  else if(ActionEventType == FailBuffersPreventingStart)
16628  {
16629  ErrorLog = " facing buffers and unable to start at ";
16630  }
16631  else if(ActionEventType == FailDerailed)
16632  {
16633  ErrorLog = " DERAILED at position ";
16634  Prefix = " DERAILMENT: ";
16635  Derailments++;
16636  }
16637  else if(ActionEventType == FailBufferCrash)
16638  {
16639  ErrorLog = " CRASHED INTO BUFFERS at ";
16640  Prefix = " CRASH: ";
16641  CrashedTrains++;
16642  }
16643  else if(ActionEventType == FailLevelCrossingCrash)
16644  {
16645  ErrorLog = " CRASHED INTO ROAD TRAFFIC AT A LEVEL CROSSING at ";
16646  Prefix = " CRASH: ";
16647  CrashedTrains++;
16648  }
16649  else if(ActionEventType == FailCrashed)
16650  {
16651  ErrorLog = " CRASHED INTO " + OtherHeadCode + " at position ";
16652  Prefix = " CRASH: ";
16653  CrashedTrains++;
16654  CrashedTrains++;
16655  }
16656  else if(ActionEventType == FailSPAD)
16657  {
16658  ErrorLog = " PASSED SIGNAL AT DANGER at position ";
16659  Prefix = " SPAD: ";
16660  SPADEvents++;
16661  }
16662  else if(ActionEventType == FailLockedRoute)
16663  {
16664  ErrorLog = "Signals reset ahead of train, route cancelled at position ";
16665  Prefix = " SPAD RISK: ";
16666  SPADRisks++;
16667  }
16668  else if(ActionEventType == RouteForceCancelled)
16669  {
16670  ErrorLog = " forced a route cancellation by occupying it incorrectly at ";
16671  }
16672  else if(ActionEventType == WaitingForJBO)
16673  {
16674  Prefix = " WARNING: ";
16675  ErrorLog = " waiting to join " + OtherHeadCode + " at ";
16676  WarningStr = " waiting to join " + OtherHeadCode + " at ";
16677  Display->WarningLog(8, TimeAndHeadCode + WarningStr + LocationID);
16678  }
16679  else if(ActionEventType == WaitingForFJO)
16680  {
16681  Prefix = " WARNING: ";
16682  ErrorLog = " waiting to be joined by " + OtherHeadCode + " at ";
16683  WarningStr = " waiting to be joined by " + OtherHeadCode + " at ";
16684  Display->WarningLog(9, TimeAndHeadCode + WarningStr + LocationID);
16685  }
16686  else if(ActionEventType == FailNoPowerUnableToDepart) //06:00:10: WARNING: 2F43 is without power so it can't depart from Essex Road // added at v2.19.1
16687  {
16688  Prefix = " WARNING: ";
16689  ErrorLog = " is without power so it can't depart from ";
16690  WarningStr = " is without power so it can't depart from ";
16691  Display->WarningLog(27, TimeAndHeadCode + WarningStr + LocationID);
16692  }
16693  else if(ActionEventType == FailTrainInFront) //06:00:10: WARNING: 2F43 can't depart because there is a train in front at Essex Road // added at v2.19.1
16694  {
16695  Prefix = " WARNING: ";
16696  ErrorLog = " can't depart because there is a train in front at ";
16697  WarningStr = " can't depart because there is a train in front at ";
16698  Display->WarningLog(28, TimeAndHeadCode + WarningStr + LocationID);
16699  }
16700 
16701  BaseLog = Utilities->Format96HHMMSS(ActualTime) + Prefix + HeadCode;
16702  PerfLogForm->PerformanceLog(4, BaseLog + ErrorLog + LocationID);
16703  Utilities->CallLogPop(1371);
16704 }
16705 
16706 // ---------------------------------------------------------------------------
16707 
16709 {
16710 /* //for testing purposes
16711  TrainDataEntry
16712  AnsiString HeadCode, Description;//null on creation
16713  int StartSpeed, MaxRunningSpeed;//both kph
16714  int RepeatNumber;
16715  TActionVector ActionVector;
16716  TTrainOperatingDataVector TrainOperatingDataVector;//no of repeats + 1
16717  TTrainDataEntry() {StartSpeed=0; MaxRunningSpeed=0; RepeatNumber=0;}
16718 
16719  ActionVectorEntry
16720  TTimetableEntryType FormatType;
16721  TDateTime EventTime, ArrivalTime, DepartureTime;//zeroed on creation so change to -1 as a marker for 'not set'
16722  AnsiString LocationName, Command, OtherHeadCode;//null on creation
16723  TActionVectorEntry *OtherHeadCodeStartingEntryPtr;
16724  int RearStartOrRepeatMins, FrontStartOrRepeatDigits;
16725  int RepeatNumber;
16726 
16727  TrainOperatingData
16728  int Mass, MaxBrakeRate, PowerAtRail;//kg;m/s/s;W
16729  int TrainID;
16730  TRunningEntry RunningEntry;
16731  TDateTime StartTime;
16732  AnsiString HeadCode;
16733 */
16734  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveTrainDataVectorToFile");
16735  std::ofstream OutFile("TrainData.csv");
16736 
16737  if(OutFile == 0)
16738  {
16739  ShowMessage("Output file TrainData.csv failed to open");
16740  Utilities->CallLogPop(1372);
16741  return;
16742  }
16743  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
16744  {
16745  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
16746  OutFile << "HeadCode" << ',' << "Description" << ',' << "StartSpeed" << ',' << "MaxRunningSpeed" << ',' << "NumberOfTrains" << '\n' << '\n';
16747 
16748  OutFile << TDEntry.HeadCode.c_str() << ',' << TDEntry.FixedDescription.c_str() /* name changed at v2.16.1*/
16749  << ',' << TDEntry.StartSpeed << ',' << TDEntry.MaxRunningSpeed << ',' << TDEntry.NumberOfTrains << '\n' << '\n';
16750 
16751  OutFile << ',' << "FormatType" << ',' << "EventTime" << ',' << "ArrivalTime" << ',' << "DepartureTime" << ',' << "LocationName" << ',' << "Command" <<
16752  ',' << "OtherHeadCode" << ',' << "LinkedTrainEntryPtr" << ',' << "RearStartOrRepeatMins" << ',' << "FrontStartOrRepeatDigits" << ',' <<
16753  "RepeatNumber" << '\n' << '\n';
16754  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
16755  {
16756  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
16757  AnsiString TimetableEntryTypeStr;
16758  // NoFormat, TimeLoc, TimeTimeLoc, TimeCmd, StartNew, TimeCmdHeadCode, FinRemHere, FNSNonRepeatToShuttle, SNTShuttle, SNSShuttle, SNSNonRepeatFromShuttle, FSHNewService, Repeat
16759  switch(AVEntry.FormatType)
16760  {
16761  case 0:
16762  {
16763  TimetableEntryTypeStr = "NoFormat";
16764  break;
16765  }
16766 
16767  case 1:
16768  {
16769  TimetableEntryTypeStr = "TimeLoc";
16770  break;
16771  }
16772 
16773  case 2:
16774  {
16775  TimetableEntryTypeStr = "TimeTimeLoc";
16776  break;
16777  }
16778 
16779  case 3:
16780  {
16781  TimetableEntryTypeStr = "TimeCmd";
16782  break;
16783  }
16784 
16785  case 4:
16786  {
16787  TimetableEntryTypeStr = "StartNew";
16788  break;
16789  }
16790 
16791  case 5:
16792  {
16793  TimetableEntryTypeStr = "TimeCmdHeadCode";
16794  break;
16795  }
16796 
16797  case 6:
16798  {
16799  TimetableEntryTypeStr = "FinRemHere";
16800  break;
16801  }
16802 
16803  case 7:
16804  {
16805  TimetableEntryTypeStr = "FNSShuttle";
16806  break;
16807  }
16808 
16809  case 8:
16810  {
16811  TimetableEntryTypeStr = "SNTShuttle";
16812  break;
16813  }
16814 
16815  case 9:
16816  {
16817  TimetableEntryTypeStr = "SNSShuttle";
16818  break;
16819  }
16820 
16821  case 10:
16822  {
16823  TimetableEntryTypeStr = "SNSNonRepeatFromShuttle";
16824  break;
16825  }
16826 
16827  case 11:
16828  {
16829  TimetableEntryTypeStr = "FSHNewService";
16830  break;
16831  }
16832 
16833  case 12:
16834  {
16835  TimetableEntryTypeStr = "Repeat";
16836  break;
16837  }
16838 
16839  default:
16840  {
16841  TimetableEntryTypeStr = "Default";
16842  break;
16843  }
16844  }
16845  OutFile << ',' << TimetableEntryTypeStr.c_str() << ',' << Utilities->Format96HHMM(AVEntry.EventTime).c_str() << ',' << Utilities->Format96HHMM
16846  (AVEntry.ArrivalTime).c_str() << ',' << Utilities->Format96HHMM(AVEntry.DepartureTime).c_str() << ',' << AVEntry.LocationName.c_str()
16847  << ',' << AVEntry.Command.c_str() << ',' << AVEntry.OtherHeadCode.c_str()
16848  << ',' << AVEntry.LinkedTrainEntryPtr << ',' << AVEntry.RearStartOrRepeatMins << ',' << AVEntry.FrontStartOrRepeatDigits << ',' <<
16849  AVEntry.NumberOfRepeats << '\n';
16850  }
16851  OutFile << '\n';
16852  OutFile << ',' << ',' << "Mass" << ',' << "MaxBrakeRate" << ',' << "PowerAtRail" << ',' << "TrainID" << ',' << "RunningEntry" << '\n' << '\n';
16853  for(unsigned int y = 0; y < TrainDataVector.at(x).TrainOperatingDataVector.size(); y++)
16854  {
16855  TTrainOperatingData TOD = TrainDataVector.at(x).TrainOperatingDataVector.at(y);
16856  AnsiString RunningEntryStr;
16857  // NotStarted, Running, Exited
16858  switch(TOD.RunningEntry)
16859  {
16860  case 0:
16861  {
16862  RunningEntryStr = "NotStarted";
16863  break;
16864  }
16865 
16866  case 1:
16867  {
16868  RunningEntryStr = "Running";
16869  break;
16870  }
16871 
16872  case 2:
16873  {
16874  RunningEntryStr = "Exited";
16875  break;
16876  }
16877  }
16878  OutFile << ',' << ',' << TOD.TrainID << ',' << RunningEntryStr.c_str() << ',' << '\n';
16879  }
16880  OutFile << '\n';
16881  }
16882  OutFile.close();
16883  Utilities->CallLogPop(1373);
16884 }
16885 
16886 // ---------------------------------------------------------------------------
16887 
16888 void TTrainController::StopTTClockMessage(int Caller, AnsiString Message)
16889 // ShowMessage stops everything so this function used where a message is needed when may be in Operating mode.
16890 // The timetable Restart and BaseTimes are reset so the timetable clock stops & restarts when 'OK' button pressed (in ClockTimer2 when StopTTClockFlag is false)
16891 {
16892  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",StopTTClockMessage," + Message);
16893  StopTTClockFlag = true; // so TTClock stopped during MasterClockTimer function
16895  ShowMessage(Message);
16896  BaseTime = TDateTime::CurrentDateTime();
16897  StopTTClockFlag = false;
16898  Utilities->CallLogPop(1374);
16899 }
16900 
16901 // ---------------------------------------------------------------------------
16902 
16903 void TTrainController::SaveSessionTrains(int Caller, std::ofstream &SessionFile)
16904 // save *TrainDataEntryPtr & *ActionVectorEntryPtr as integer offsets
16905 // from the start of the relevant vectors. Can't save the pointer values
16906 // as these will be different each time the vectors are created
16907 {
16908  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveSessionTrains");
16909  Utilities->SaveFileInt(SessionFile, TrainVector.size());
16910  for(unsigned int x = 0; x < TrainVector.size(); x++)
16911  {
16912  TrainVectorAt(55, x).SaveOneSessionTrain(0, SessionFile);
16913  }
16914  Utilities->CallLogPop(1375);
16915 }
16916 
16917 // ---------------------------------------------------------------------------
16918 
16919 void TTrainController::LoadSessionTrains(int Caller, std::ifstream &SessionFile)
16920 {
16921  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LoadSessionTrains");
16922  int NumberOfTrains = Utilities->LoadFileInt(SessionFile);
16923  TTrain *NewTrain = new TTrain(1, 0, 0, "", 0, 1, 0, 0, 0, (TTrainMode)0, 0, 0, 0, 0, 0); // have to have >0 for mass, else have divide
16924  // by zero error in calculating AValue, use 1
16925  for(int x = 0; x < NumberOfTrains; x++)
16926  {
16927  *NewTrain = TTrain(2, 0, 0, "", 0, 1, 0, 0, 0, (TTrainMode)0, 0, 0, 0, 0, 0); // have to have >0 for mass, else have divide
16928  // by zero error in calculating AValue, use 1
16929  NewTrain->LoadOneSessionTrain(0, SessionFile);
16930  if((NewTrain->EntrySpeed < 1) && (NewTrain->PowerAtRail < 1))
16931  // added at v2.4.0. have to include as that value not stored in session file
16932  {
16933  NewTrain->StoppedWithoutPower = true;
16934  }
16935  TrainVector.push_back(*NewTrain);
16936  LastTrainLoaded = x;
16937  }
16938  delete NewTrain;
16939  Utilities->CallLogPop(1376);
16940 }
16941 
16942 // ---------------------------------------------------------------------------
16943 
16944 bool TTrainController::CheckSessionTrains(int Caller, std::ifstream &InFile)
16945 {
16946  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckSessionTrains");
16947  int NumberOfTrains;
16948 
16949  if(!Utilities->CheckAndReadFileInt(InFile, 0, 10000, NumberOfTrains))
16950  {
16951  Utilities->CallLogPop(1377);
16952  return(false);
16953  }
16954  for(int x = 0; x < NumberOfTrains; x++)
16955  {
16956  if(!(TTrain::CheckOneSessionTrain(InFile)))
16957  {
16958  Utilities->CallLogPop(1378);
16959  return(false);
16960  }
16961  }
16962  Utilities->CallLogPop(1379);
16963  return(true);
16964 }
16965 
16966 // ---------------------------------------------------------------------------
16967 
16968 void TTrainController::SaveSessionLockedRoutes(int Caller, std::ofstream &SessionFile)
16969 {
16970  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveSessionLockedRoutes");
16971  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.size());
16972  for(unsigned int x = 0; x < AllRoutes->LockedRouteVector.size(); x++)
16973  {
16974  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.at(x).RouteNumber);
16975  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.at(x).RearTrackVectorPosition);
16976  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.at(x).LastTrackVectorPosition);
16977  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.at(x).LastXLinkPos);
16978  Utilities->SaveFileDouble(SessionFile, double(AllRoutes->LockedRouteVector.at(x).LockStartTime));
16979  }
16980  Utilities->CallLogPop(1380);
16981 }
16982 
16983 // ---------------------------------------------------------------------------
16984 
16985 void TTrainController::LoadSessionLockedRoutes(int Caller, std::ifstream &SessionFile)
16986 {
16987  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LoadSessionLockedRoutes");
16988  TAllRoutes::TLockedRouteClass LockedRouteObject;
16989  int LockedRouteVectorSize = Utilities->LoadFileInt(SessionFile);
16990 
16991  for(int x = 0; x < LockedRouteVectorSize; x++)
16992  {
16993  LockedRouteObject.RouteNumber = Utilities->LoadFileInt(SessionFile);
16994  LockedRouteObject.RearTrackVectorPosition = Utilities->LoadFileInt(SessionFile);
16995  LockedRouteObject.LastTrackVectorPosition = Utilities->LoadFileInt(SessionFile);
16996  LockedRouteObject.LastXLinkPos = Utilities->LoadFileInt(SessionFile);
16997  double LockStartTimeDouble = Utilities->LoadFileDouble(SessionFile);
16998  LockedRouteObject.LockStartTime = TDateTime(LockStartTimeDouble);
16999  AllRoutes->LockedRouteVector.push_back(LockedRouteObject);
17000  }
17001  Utilities->CallLogPop(1381);
17002 }
17003 
17004 // ---------------------------------------------------------------------------
17005 
17006 bool TTrainController::CheckSessionLockedRoutes(int Caller, std::ifstream &SessionFile)
17007 {
17008  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckSessionLockedRoutes");
17009  int LockedRouteVectorSize;
17010 
17011  if(!Utilities->CheckAndReadFileInt(SessionFile, 0, 10000, LockedRouteVectorSize))
17012  {
17013  Utilities->CallLogPop(1382);
17014  return(false);
17015  }
17016  for(int x = 0; x < LockedRouteVectorSize; x++)
17017  {
17018  if(!Utilities->CheckFileInt(SessionFile, 0, 1000000))
17019  {
17020  Utilities->CallLogPop(1383);
17021  return(false);
17022  }
17023  if(!Utilities->CheckFileInt(SessionFile, 0, 1000000))
17024  {
17025  Utilities->CallLogPop(1384);
17026  return(false);
17027  }
17028  if(!Utilities->CheckFileInt(SessionFile, 0, 1000000))
17029  {
17030  Utilities->CallLogPop(1385);
17031  return(false);
17032  }
17033  if(!Utilities->CheckFileInt(SessionFile, 0, 3))
17034  {
17035  Utilities->CallLogPop(1386);
17036  return(false);
17037  }
17038  if(!Utilities->CheckFileDouble(SessionFile))
17039  {
17040  Utilities->CallLogPop(1387);
17041  return(false);
17042  }
17043  }
17044  Utilities->CallLogPop(1388);
17045  return(true);
17046 }
17047 
17048 // ---------------------------------------------------------------------------
17049 
17050 void TTrainController::SaveSessionContinuationAutoSigEntries(int Caller, std::ofstream &SessionFile)
17051 {
17052  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveSessionContinuationAutoSigEntries");
17053  Utilities->SaveFileInt(SessionFile, ContinuationAutoSigVector.size());
17054  for(unsigned int x = 0; x < ContinuationAutoSigVector.size(); x++)
17055  {
17056  Utilities->SaveFileInt(SessionFile, ContinuationAutoSigVector.at(x).RouteNumber);
17057  Utilities->SaveFileInt(SessionFile, ContinuationAutoSigVector.at(x).AccessNumber);
17058  Utilities->SaveFileDouble(SessionFile, ContinuationAutoSigVector.at(x).FirstDelay);
17059  Utilities->SaveFileDouble(SessionFile, ContinuationAutoSigVector.at(x).SecondDelay);
17060  Utilities->SaveFileDouble(SessionFile, ContinuationAutoSigVector.at(x).ThirdDelay);
17061  Utilities->SaveFileDouble(SessionFile, double(ContinuationAutoSigVector.at(x).PassoutTime));
17062  }
17063  Utilities->CallLogPop(1389);
17064 }
17065 
17066 // ---------------------------------------------------------------------------
17067 
17068 void TTrainController::LoadSessionContinuationAutoSigEntries(int Caller, std::ifstream &SessionFile)
17069 {
17070  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LoadSessionContinuationAutoSigEntries");
17071  TContinuationAutoSigEntry ContinuationAutoSigObject;
17072  int ContinuationAutoSigVectorSize = Utilities->LoadFileInt(SessionFile);
17073 
17074  for(int x = 0; x < ContinuationAutoSigVectorSize; x++)
17075  {
17076  ContinuationAutoSigObject.RouteNumber = Utilities->LoadFileInt(SessionFile);
17077  ContinuationAutoSigObject.AccessNumber = Utilities->LoadFileInt(SessionFile);
17078  ContinuationAutoSigObject.FirstDelay = Utilities->LoadFileDouble(SessionFile);
17079  ContinuationAutoSigObject.SecondDelay = Utilities->LoadFileDouble(SessionFile);
17080  ContinuationAutoSigObject.ThirdDelay = Utilities->LoadFileDouble(SessionFile);
17081  double PassoutTimeDouble = Utilities->LoadFileDouble(SessionFile);
17082  ContinuationAutoSigObject.PassoutTime = TDateTime(PassoutTimeDouble);
17083  ContinuationAutoSigVector.push_back(ContinuationAutoSigObject);
17084  }
17085  Utilities->CallLogPop(1390);
17086 }
17087 
17088 // ---------------------------------------------------------------------------
17089 
17090 bool TTrainController::CheckSessionContinuationAutoSigEntries(int Caller, std::ifstream &SessionFile)
17091 {
17092  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckSessionContinuationAutoSigEntries");
17093  int ContinuationAutoSigVectorSize;
17094 
17095  if(!Utilities->CheckAndReadFileInt(SessionFile, 0, 10000, ContinuationAutoSigVectorSize))
17096  {
17097  Utilities->CallLogPop(1391);
17098  return(false);
17099  }
17100  for(int x = 0; x < ContinuationAutoSigVectorSize; x++)
17101  {
17102  if(!Utilities->CheckFileInt(SessionFile, 0, 1000000))
17103  {
17104  Utilities->CallLogPop(1392);
17105  return(false);
17106  }
17107  if(!Utilities->CheckFileInt(SessionFile, 0, 3))
17108  {
17109  Utilities->CallLogPop(1393);
17110  return(false);
17111  }
17112  if(!Utilities->CheckFileDouble(SessionFile))
17113  {
17114  Utilities->CallLogPop(1405);
17115  return(false);
17116  }
17117  if(!Utilities->CheckFileDouble(SessionFile))
17118  {
17119  Utilities->CallLogPop(1406);
17120  return(false);
17121  }
17122  if(!Utilities->CheckFileDouble(SessionFile))
17123  {
17124  Utilities->CallLogPop(1407);
17125  return(false);
17126  }
17127  if(!Utilities->CheckFileDouble(SessionFile))
17128  {
17129  Utilities->CallLogPop(1394);
17130  return(false);
17131  }
17132  }
17133  Utilities->CallLogPop(1395);
17134  return(true);
17135 }
17136 
17137 // ---------------------------------------------------------------------------
17138 
17139 /*
17140  class TContinuationTrainExpectationEntry //for expected trains at continuation entries
17141  {
17142  public:
17143  AnsiString Description; ///< service description
17144  AnsiString HeadCode; ///< service headcode
17145  int RepeatNumber; ///< service RepeatNumber
17146  int IncrementalMinutes; ///< Repeat separation in minutes
17147  int IncrementalDigits; ///< Repeat headcode separation
17148  int VectorPosition; ///< TrackVectorPosition for the continuation element
17149  TTrainDataEntry *TrainDataEntryPtr; ///< points to the service entry in the timetable's TrainDataVector
17150  };
17151 
17152 
17153  typedef std::multimap<TDateTime,TContinuationTrainExpectationEntry> TContinuationTrainExpectationMultiMap;
17154  typedef pair<TDateTime, TContinuationTrainExpectationEntry> TContinuationTrainExpectationMultiMapPair;
17155 */
17156 
17158 // build this into timetable load so session loading can use it too
17159 // being a multimap it automatically sorts in ascending EventTime order
17160 {
17161  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",BuildContinuationTrainExpectationMultiMap");
17163  // need to clear as this called twice when load a session
17164  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
17165  {
17166  TTrainDataEntry & TDEntry = TrainDataVector.at(x);
17167  const TActionVectorEntry &AVFirstEntry = TDEntry.ActionVector.at(0);
17168  const TActionVectorEntry &AVLastEntry = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
17169 
17170  if(AVFirstEntry.Command == "Snt")
17171  // new train (no need to include Snt-sh since they can't start at a continuation)
17172  {
17175  {
17177  CTEEntry.VectorPosition = AVFirstEntry.RearStartOrRepeatMins;
17178  // retains this value for all repeats
17179  CTEEntry.RepeatNumber = 0; // for first entry
17180  CTEEntry.TrainDataEntryPtr = &TDEntry;
17181  // retains this value for all repeats
17182  CTEEntry.HeadCode = TDEntry.HeadCode;
17183  CTEEntry.FixedDescription = TDEntry.FixedDescription; //name changed at v2.16.1
17184  CTEEntry.IncrementalMinutes = 0;
17185  CTEEntry.IncrementalDigits = 0;
17186  if(AVLastEntry.FormatType == Repeat)
17187  {
17188  CTEEntry.IncrementalMinutes = AVLastEntry.RearStartOrRepeatMins;
17189  // retains this value or 0 for all repeats
17190  CTEEntry.IncrementalDigits = AVLastEntry.FrontStartOrRepeatDigits;
17191  // retains this value or 0 for all repeats
17192  }
17193  CTEMMP.first = AVFirstEntry.EventTime;
17194  CTEMMP.second = CTEEntry;
17195  ContinuationTrainExpectationMultiMap.insert(CTEMMP);
17196  // base entry
17197  if(TDEntry.NumberOfTrains > 1)
17198  {
17199  if(AVLastEntry.FormatType != Repeat)
17200  {
17201  throw Exception("Error, Last ActionVectorEntry not a repeat in BuildContinuationTrainExpectationMultiMap");
17202  }
17203  for(int y = 1; y < TDEntry.NumberOfTrains; y++)
17204  {
17205  CTEEntry.RepeatNumber = y;
17206  CTEEntry.HeadCode = GetRepeatHeadCode(23, TDEntry.HeadCode, y, AVLastEntry.FrontStartOrRepeatDigits);
17207  // CTEEntry.VectorPosition stays same
17208  CTEMMP.first = GetRepeatTime(3, AVFirstEntry.EventTime, y, AVLastEntry.RearStartOrRepeatMins);
17209  CTEMMP.second = CTEEntry;
17210  ContinuationTrainExpectationMultiMap.insert(CTEMMP);
17211  }
17212  }
17213  }
17214  }
17215  }
17216  Utilities->CallLogPop(1396);
17217 }
17218 
17219 // ---------------------------------------------------------------------------
17220 
17222 {
17223  // called when WarningFlashCount == 0 or when press zoomout button
17224  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrainsInZoomOutMode");
17225  if(!Display->ZoomOutFlag)
17226  {
17227  Utilities->CallLogPop(1156);
17228  return;
17229  }
17230  for(unsigned int x = 0; x < TrainVector.size(); x++)
17231  {
17232  // plot blanks & track for all train, even if to be overplotted, since when flashing need to overplot all anyway
17233  // if OldPlotElement[x] == -1 then ignore (not plotted)
17235  TrainVectorAt(57, x).PlotTrainInZoomOutMode(0, Flash);
17236  }
17237  Display->Update();
17238  // need to keep this since Update() not called for PlotSmallOutput as too slow
17239  Utilities->CallLogPop(742);
17240 }
17241 
17242 // ---------------------------------------------------------------------------
17243 
17244 TTrain &TTrainController::TrainVectorAt(int Caller, int VecPos)
17245 {
17246  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainVectorAt," + AnsiString(VecPos));
17247  if((VecPos < 0) || (VecPos >= (int)TrainVector.size()))
17248  {
17249  throw Exception("Out of Range Error, vector size: " + AnsiString(TrainVector.size()) + ", VecPos: " + AnsiString(VecPos) + " in TrainVectorAt");
17250  }
17251  Utilities->CallLogPop(740);
17252  return(TrainVector.at(VecPos));
17253 }
17254 
17255 // ---------------------------------------------------------------------------
17256 
17257 void TTrainController::CreateFormattedTimetable(int Caller, AnsiString RailwayTitle, AnsiString TimetableTitle, AnsiString CurDir)
17258 {
17259  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CreateFormattedTimetable");
17260  AnsiString RetStr = "", PartStr = "";
17261 
17262 
17263 /*
17264  Have description & mass etc for train at top - header, then array of actions
17265 
17266  class TActionVectorEntry
17267  {
17268  public:
17269  AnsiString LocationName, Command, OtherHeadCode, NonRepeatingShuttleLinkHeadCode;
17271  bool SignallerControl;
17273  bool Warning;
17275  int NumberOfRepeats;
17277  int RearStartOrRepeatMins, FrontStartOrRepeatDigits;
17279  TDateTime EventTime, ArrivalTime, DepartureTime;
17281  TNumList ExitList;
17283  TTimetableFormatType FormatType;
17285  TTimetableLocationType LocationType;
17287  TTimetableSequenceType SequenceType;
17289  TTimetableShuttleLinkType ShuttleLinkType;
17291  TTrainDataEntry *LinkedTrainEntryPtr;
17293  TTrainDataEntry *NonRepeatingShuttleLinkEntryPtr;
17295 
17296  typedef std::vector<TActionVectorEntry> TActionVector;//contains all actions for a single train
17297 
17298  enum TRunningEntry {NotStarted, Running, Exited};//contains status info for each train
17299 
17300  class TTrainOperatingData
17301  {
17302  public:
17303  int TrainID;
17304  TActionEventType EventReported;
17305  TRunningEntry RunningEntry;
17306 
17307  //inline function
17308  TTrainOperatingData() {TrainID = -1; EventReported= NoEvent; RunningEntry=NotStarted;}//ID -1 = marker for not running
17309  };
17310 
17311  typedef std::vector<TTrainOperatingData> TTrainOperatingDataVector;
17312 
17313  class TTrainDataEntry
17314  {
17315  public:
17316  AnsiString HeadCode, ServiceReference, Description;
17318  double MaxBrakeRate;
17320  double MaxRunningSpeed;
17322  double PowerAtRail;
17324  int Mass;
17326  int NumberOfTrains;
17328  int SignallerSpeed;
17330  int StartSpeed;
17332  TActionVector ActionVector;
17334  TTrainOperatingDataVector TrainOperatingDataVector;
17336 
17337  //inline function
17338  TTrainDataEntry() {StartSpeed=0; MaxRunningSpeed=0; NumberOfTrains=0;}
17339  };
17340 
17341  typedef std::vector<TTrainDataEntry> TTrainDataVector;//object is a member of TTrainController & contains the whole timetable
17342 
17343  //formatted timetable types
17344  class TOneTrainFormattedEntry
17345  {
17346  AnsiString Action;//includes location if relevanr
17347  AnsiString Time;
17348  };
17349 
17350  typedef std::vector<TOneTrainFormattedEntry> TOneFormattedTrainVector;
17351 
17352  class TOneCompleteFormattedTrain//headcode + list of actions
17353  {
17354  public:
17355  AnsiString HeadCode;
17356  TOneFormattedTrainVector OneFormattedTrainVector;
17357  };
17358 
17359  typedef std::vector<TOneCompleteFormattedTrain> TOneCompleteFormattedTrainVector;//list af all repeats
17360 
17361  class TTrainFormattedInformation//contains all information for a single TT entry (including repeats)
17362  {
17363  public:
17364  AnsiString Header;//description, mass, power, brake rate etc
17365  int NumberOfTrains;// number of repeats + 1
17366  TOneCompleteFormattedTrainVector OneCompleteFormattedTrainVector;//list af all repeats
17367  };
17368 
17369 
17370  typedef std::vector<TTrainFormattedInformation> TAllFormattedTrains;//all timetable in formatted form
17371  //end of formatted timetable types
17372 
17373 */
17374 
17375  AnsiString TTFileName = TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss");
17376 
17377  // format "16/06/2009 20:55:17"
17378  // avoid characters in filename:= / \ : * ? " < > |
17379  TTFileName = CurDir + "\\Formatted timetables\\Timetable " + TTFileName + "; " + RailwayTitle + "; " + TimetableTitle + ".csv";
17380 
17381  AnsiString ShortTTName = "";
17382 
17383  for(int x = TTFileName.Length(); x > 0; x--)
17384  {
17385  if(TTFileName[x] == '\\')
17386  {
17387  ShortTTName = TTFileName.SubString(x + 1, TTFileName.Length() - x - 4);
17388  break;
17389  }
17390  }
17391 
17392  ShowMessage("Creates two timetables named " + ShortTTName +
17393  " in the 'Formatted timetables' folder, one in service order in '.csv' format, and one in chronological order in '.txt' format");
17394 
17395  Screen->Cursor = TCursor(-11); // Hourglass
17396 
17397  AnsiString FormatNoDPStr = "#######0";
17398  AnsiString TableTitle = "", TimetableTimeStr = "", MassStr = "", PowerStr = "", BrakeStr = "", MaxSpeedStr = "", FirstHeadCode = "", Header = "";
17399 
17401  TableTitle = "Railway: " + RailwayTitle + "; Timetable: " + TimetableTitle + "; Start time: " + TimetableTimeStr;
17402  TAllFormattedTrains *AllTTTrains = new TAllFormattedTrains;
17403 
17404  // all timetable in formatted form
17405  //create the AllTTTrains vector
17406  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
17407  {
17408  MassStr = "", PowerStr = "", BrakeStr = "", MaxSpeedStr = "";
17409  const TTrainDataEntry &TrainDataEntry = TrainDataVector.at(x);
17410  if(TrainDataEntry.Mass > 0)
17411  {
17412  MassStr = "; Mass " + AnsiString::FormatFloat(FormatNoDPStr, ((double)TrainDataEntry.Mass) / 1000) + "Te; ";
17413  }
17414  if(TrainDataEntry.PowerAtRail > 0)
17415  {
17416  PowerStr = "Power " + AnsiString::FormatFloat(FormatNoDPStr, TrainDataEntry.PowerAtRail / 1000 / 0.8) + "kW; ";
17417  }
17418  if(TrainDataEntry.MaxBrakeRate > 0)
17419  {
17420  BrakeStr = "Brake force " + AnsiString::FormatFloat(FormatNoDPStr, (TrainDataEntry.MaxBrakeRate * TrainDataEntry.Mass / 9810)) + "Te; ";
17421  }
17422  if(TrainDataEntry.MaxRunningSpeed > 0)
17423  {
17424  MaxSpeedStr = "Maximum speed " + AnsiString::FormatFloat(FormatNoDPStr, TrainDataEntry.MaxRunningSpeed) + " km/h";
17425  }
17426  FirstHeadCode = TrainDataEntry.HeadCode;
17427  int IncDigits = 0, IncMinutes = 0;
17428  const TActionVector &ActionVector = TrainDataEntry.ActionVector;
17429  if(!ActionVector.empty())
17430  {
17431  if(ActionVector.at(ActionVector.size() - 1).FormatType == Repeat)
17432  {
17433  IncDigits = ActionVector.at(ActionVector.size() - 1).FrontStartOrRepeatDigits;
17434  IncMinutes = ActionVector.at(ActionVector.size() - 1).RearStartOrRepeatMins;
17435  }
17436  }
17437  TTrainFormattedInformation OneTTLine;
17438  // contains all information for a single TT entry (including repeats)
17439  for(int y = 0; y < TrainDataEntry.NumberOfTrains; y++)
17440  {
17441  OneTTLine.Header = "";
17442  if((TrainDataEntry.FixedDescription != "") && (MassStr != "")) //name changed at v2.16.1
17443  {
17444  OneTTLine.Header = TrainDataEntry.FixedDescription + MassStr + PowerStr + BrakeStr + MaxSpeedStr; //name changed at v2.16.1
17445  }
17446  else if(TrainDataEntry.FixedDescription != "") //name changed at v2.16.1
17447  {
17448  OneTTLine.Header = TrainDataEntry.FixedDescription; //name changed at v2.16.1
17449  }
17450  OneTTLine.NumberOfTrains = TrainDataEntry.NumberOfTrains;
17451  TOneCompleteFormattedTrain OneTTTrain; // headcode + list of actions
17452  for(unsigned int z = 0; z < ActionVector.size(); z++)
17453  {
17454  TOneTrainFormattedEntry OneTTEntry;
17455  OneTTTrain.HeadCode = GetRepeatHeadCode(24, FirstHeadCode, y, IncDigits);
17456  TActionVectorEntry ActionVectorEntry = ActionVector.at(z);
17457  AnsiString PartStr = "", TimeStr = "";
17458 /*
17459  enum TTimetableFormatType {NoFormat, TimeLoc, TimeTimeLoc, TimeCmd, StartNew, TimeCmdHeadCode, FinRemHere,
17460  FNSNonRepeatToShuttle, SNTShuttle, SNSShuttle, SNSNonRepeatFromShuttle, FSHNewService, Repeat, PassTime,
17461  ExitRailway};
17462  enum TTimetableSequenceType {NoSequence, StartSequence, FinishSequence, IntermediateSequence, SequTypeForRepeatEntry};
17463  enum TTimetableLocationType {NoLocation, AtLocation, EnRoute, LocTypeForRepeatEntry};
17464  enum TTimetableShuttleLinkType {NoShuttleLink, NotAShuttleLink, ShuttleLink, ShuttleLinkTypeForRepeatEntry};
17465 */
17466  if(ActionVectorEntry.SequenceType == StartSequence)
17467  {
17468  if(ActionVectorEntry.FormatType == StartNew)
17469  {
17470  if(ActionVectorEntry.LocationName != "")
17471  {
17472  if(Track->TrackElementAt(742, ActionVectorEntry.RearStartOrRepeatMins).TrackType == Continuation)
17473  {
17474  PartStr = "Enters at " + ActionVectorEntry.LocationName;
17475  }
17476  else
17477  {
17478  PartStr = "Created at " + ActionVectorEntry.LocationName;
17479  }
17480  }
17481  else // may be a named continuation or other element, and if so report that
17482  {
17483  AnsiString LocName = Track->TrackElementAt(739, ActionVectorEntry.RearStartOrRepeatMins).ActiveTrackElementName;
17484  if(Track->TrackElementAt(740, ActionVectorEntry.RearStartOrRepeatMins).TrackType == Continuation)
17485  {
17486  if(LocName != "")
17487  {
17488  PartStr = "Enters at " + LocName;
17489  }
17490  else // use rear position if it's a continuation
17491  {
17492  PartStr = "Enters at " + Track->TrackElementAt(737, ActionVectorEntry.RearStartOrRepeatMins).ElementID;
17493  }
17494  }
17495  else // not a continuation
17496  {
17497  if(LocName != "")
17498  // if not a continuation then LocName should be same as ActionVectorEntry.LocationName
17499  // but include anyway
17500  {
17501  PartStr = "Created at " + LocName;
17502  }
17503  else // use rear position again
17504  {
17505  PartStr = "Created at " + Track->TrackElementAt(741, ActionVectorEntry.RearStartOrRepeatMins).ElementID;
17506  }
17507  }
17508  }
17509  TimeStr = Utilities->Format96HHMM(GetRepeatTime(20, ActionVectorEntry.EventTime, y, IncMinutes));
17510  }
17511  else if(ActionVectorEntry.FormatType == SNTShuttle)
17512  {
17513  if(y == 0) // first train
17514  {
17515  PartStr = "Enters at " + ActionVectorEntry.LocationName;
17516  TimeStr = Utilities->Format96HHMM(GetRepeatTime(21, ActionVectorEntry.EventTime, y, IncMinutes));
17517  }
17518  else
17519  {
17520  PartStr = "Repeat shuttle service at " + ActionVectorEntry.LocationName + " from ";
17521  TimeStr = GetRepeatHeadCode(45, ActionVectorEntry.OtherHeadCode, y - 1, IncDigits) + " at " +
17522  Utilities->Format96HHMM(GetRepeatTime(26, ActionVectorEntry.EventTime, y, IncMinutes));
17523  } // y-1 for headcode above since it is the last repeat value that the train is from
17524 
17525  }
17526  else if(ActionVectorEntry.Command == "Sfs")
17527  {
17528  PartStr = "New service at " + ActionVectorEntry.LocationName + " split from";
17529  TimeStr = GetRepeatHeadCode(33, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
17530  Utilities->Format96HHMM(GetRepeatTime(24, ActionVectorEntry.EventTime, y, IncMinutes));
17531  }
17532  else if(ActionVectorEntry.Command == "Sns")
17533  {
17534  PartStr = "New service at " + ActionVectorEntry.LocationName + " from";
17535  TimeStr = GetRepeatHeadCode(34, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
17536  Utilities->Format96HHMM(GetRepeatTime(25, ActionVectorEntry.EventTime, y, IncMinutes));
17537  }
17538  else if(ActionVectorEntry.FormatType == SNSShuttle)
17539  {
17540  if(y == 0) // first entry from shuttle
17541  {
17542  PartStr = "New service at " + ActionVectorEntry.LocationName + " from";
17543  TimeStr = ActionVectorEntry.NonRepeatingShuttleLinkHeadCode + " at " +
17544  Utilities->Format96HHMM(GetRepeatTime(27, ActionVectorEntry.EventTime, y, IncMinutes));
17545  }
17546  else
17547  {
17548  PartStr = "Repeat shuttle service at " + ActionVectorEntry.LocationName + " from ";
17549  TimeStr = GetRepeatHeadCode(35, ActionVectorEntry.OtherHeadCode, y - 1, IncDigits) + " at " +
17550  Utilities->Format96HHMM(GetRepeatTime(22, ActionVectorEntry.EventTime, y, IncMinutes));
17551  } // y-1 for headcode above since it is the last repeat value that the train is from
17552 
17553  }
17554  else if(ActionVectorEntry.FormatType == SNSNonRepeatFromShuttle)
17555  {
17556  PartStr = "New service at " + ActionVectorEntry.LocationName + " from";
17557  // need repeat for the non-repeating headcode as it's the last train of the repeating shuttle
17558  TTrainDataEntry *TDE = ActionVectorEntry.LinkedTrainEntryPtr;
17559  AnsiString FirstHeadCode = TDE->HeadCode;
17560  int LastRepeatNumber = TDE->NumberOfTrains - 1;
17561  // a shuttle has to have at least 1 repeat
17562  int IncrementalDigits = TDE->ActionVector.at(TDE->ActionVector.size() - 1).FrontStartOrRepeatDigits;
17563  TimeStr = GetRepeatHeadCode(36, FirstHeadCode, LastRepeatNumber, IncrementalDigits) + " at " +
17564  Utilities->Format96HHMM(GetRepeatTime(23, ActionVectorEntry.EventTime, y, IncMinutes));
17565  }
17566  }
17567  else if(ActionVectorEntry.SequenceType == IntermediateSequence)
17568  {
17569  if(ActionVectorEntry.FormatType == TimeTimeLoc)
17570  {
17571  // here need 2 entries if times different so push the first right away & the second later
17572  // if times same just give the arrival entry
17573  if(ActionVectorEntry.DepartureTime != ActionVectorEntry.ArrivalTime)
17574  {
17575  PartStr = "Arrives at " + ActionVectorEntry.LocationName;
17576  TimeStr = Utilities->Format96HHMM(GetRepeatTime(4, ActionVectorEntry.ArrivalTime, y, IncMinutes));
17577  OneTTEntry.Action = PartStr;
17578  OneTTEntry.Time = TimeStr;
17579  OneTTTrain.OneFormattedTrainVector.push_back(OneTTEntry);
17580  PartStr = "Departs from " + ActionVectorEntry.LocationName;
17581  TimeStr = Utilities->Format96HHMM(GetRepeatTime(5, ActionVectorEntry.DepartureTime, y, IncMinutes));
17582  }
17583  else
17584  {
17585  PartStr = "Arrives & departs " + ActionVectorEntry.LocationName;
17586  TimeStr = Utilities->Format96HHMM(GetRepeatTime(29, ActionVectorEntry.ArrivalTime, y, IncMinutes));
17587  }
17588  }
17589  else if((ActionVectorEntry.FormatType == TimeLoc) && (ActionVectorEntry.ArrivalTime != TDateTime(-1)))
17590  {
17591  PartStr = "Arrives at " + ActionVectorEntry.LocationName;
17592  TimeStr = Utilities->Format96HHMM(GetRepeatTime(6, ActionVectorEntry.ArrivalTime, y, IncMinutes));
17593  }
17594  else if((ActionVectorEntry.FormatType == TimeLoc) && (ActionVectorEntry.ArrivalTime == TDateTime(-1)))
17595  {
17596  PartStr = "Departs from " + ActionVectorEntry.LocationName;
17597  TimeStr = Utilities->Format96HHMM(GetRepeatTime(7, ActionVectorEntry.DepartureTime, y, IncMinutes));
17598  }
17599  else if(ActionVectorEntry.FormatType == PassTime)
17600  {
17601  PartStr = "Passes " + ActionVectorEntry.LocationName;
17602  TimeStr = Utilities->Format96HHMM(GetRepeatTime(8, ActionVectorEntry.EventTime, y, IncMinutes));
17603  }
17604  else if(ActionVectorEntry.Command == "jbo")
17605  {
17606  PartStr = "Joined at " + ActionVectorEntry.LocationName + " by";
17607  TimeStr = GetRepeatHeadCode(37, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
17608  Utilities->Format96HHMM(GetRepeatTime(9, ActionVectorEntry.EventTime, y, IncMinutes));
17609  }
17610  else if(ActionVectorEntry.Command == "fsp")
17611  {
17612  if(ActionVectorEntry.SplitDistribution != "") //new at v2.15.0
17613  {
17614  PartStr = "Splits from front [mass%-power% = " + ActionVectorEntry.SplitDistribution + "] at " + ActionVectorEntry.LocationName + " to form";
17615  }
17616  else
17617  {
17618  PartStr = "Splits from front [mass%-power% = 50-50] at " + ActionVectorEntry.LocationName + " to form";
17619  }
17620  TimeStr = GetRepeatHeadCode(38, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
17621  Utilities->Format96HHMM(GetRepeatTime(10, ActionVectorEntry.EventTime, y, IncMinutes));
17622  }
17623  else if(ActionVectorEntry.Command == "rsp")
17624  {
17625  if(ActionVectorEntry.SplitDistribution != "") //new at v2.15.0
17626  {
17627  PartStr = "Splits from front [mass%-power% = " + ActionVectorEntry.SplitDistribution + "] at " + ActionVectorEntry.LocationName + " to form";
17628  }
17629  else
17630  {
17631  PartStr = "Splits from front [mass%-power% = 50-50] at " + ActionVectorEntry.LocationName + " to form";
17632  }
17633  TimeStr = GetRepeatHeadCode(39, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
17634  Utilities->Format96HHMM(GetRepeatTime(11, ActionVectorEntry.EventTime, y, IncMinutes));
17635  }
17636  else if(ActionVectorEntry.Command == "cdt")
17637  {
17638  PartStr = "Changes direction at " + ActionVectorEntry.LocationName;
17639  TimeStr = Utilities->Format96HHMM(GetRepeatTime(12, ActionVectorEntry.EventTime, y, IncMinutes));
17640  }
17641  else if(ActionVectorEntry.Command == "dsc")
17642  {
17643  PartStr = "Changes description at " + ActionVectorEntry.LocationName;
17644  TimeStr = Utilities->Format96HHMM(GetRepeatTime(76, ActionVectorEntry.EventTime, y, IncMinutes));
17645  }
17646  }
17647  else if(ActionVectorEntry.SequenceType == FinishSequence)
17648  {
17649  if(ActionVectorEntry.Command == "Fns")
17650  {
17651  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service";
17652  TimeStr = GetRepeatHeadCode(40, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
17653  Utilities->Format96HHMM(GetRepeatTime(13, ActionVectorEntry.EventTime, y, IncMinutes));
17654  }
17655  else if(ActionVectorEntry.Command == "F-nshs")
17656  {
17657  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service";
17658  TimeStr = ActionVectorEntry.NonRepeatingShuttleLinkHeadCode + " at " + Utilities->Format96HHMM
17659  (GetRepeatTime(17, ActionVectorEntry.EventTime, y, IncMinutes));
17660  }
17661  else if((ActionVectorEntry.Command == "Fns-sh") && (y < (TrainDataEntry.NumberOfTrains - 1)))
17662  {
17663  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service ";
17664  TimeStr = GetRepeatHeadCode(41, ActionVectorEntry.OtherHeadCode, y + 1, IncDigits) + " at " +
17665  Utilities->Format96HHMM(GetRepeatTime(14, ActionVectorEntry.EventTime, y, IncMinutes));
17666  // y+1 because it's the NEXT service repeat number that is relevant
17667  }
17668  else if((ActionVectorEntry.Command == "Fns-sh") && (y >= (TrainDataEntry.NumberOfTrains - 1)))
17669  {
17670  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service";
17671  TimeStr = ActionVectorEntry.NonRepeatingShuttleLinkHeadCode + " at " + Utilities->Format96HHMM
17672  (GetRepeatTime(15, ActionVectorEntry.EventTime, y, IncMinutes));
17673  }
17674  else if((ActionVectorEntry.Command == "Frh-sh") && (y < (TrainDataEntry.NumberOfTrains - 1)))
17675  {
17676  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service";
17677  TimeStr = GetRepeatHeadCode(43, ActionVectorEntry.OtherHeadCode, y + 1, IncDigits) + " at " +
17678  Utilities->Format96HHMM(GetRepeatTime(16, ActionVectorEntry.EventTime, y, IncMinutes));
17679  // y+1 because it's the NEXT service repeat number that is relevant
17680  }
17681  else if((ActionVectorEntry.Command == "Frh-sh") && (y >= (TrainDataEntry.NumberOfTrains - 1)))
17682  {
17683  PartStr = "Terminates shuttle service at " + ActionVectorEntry.LocationName;
17684  // only used in chronological tt
17685  TimeStr = "End at " + Utilities->Format96HHMM(GetRepeatTime(28, ActionVectorEntry.EventTime, y, IncMinutes));
17686  // the "End at " is stripped out of the chronological tt but displayed in the traditional tt
17687  }
17688  else if(ActionVectorEntry.Command == "Frh")
17689  {
17690  PartStr = "Terminates at " + ActionVectorEntry.LocationName;
17691  // need here to examine the time of the preceding entry, may be ArrivalTime if TimeLoc, or EventTime otherwise
17692  if(z > 0)
17693  // should be for finish entry but include check for safety
17694  {
17695  if(ActionVector.at(z - 1).EventTime != TDateTime(-1))
17696  {
17697  TimeStr = Utilities->Format96HHMM(GetRepeatTime(30, ActionVector.at(z - 1).EventTime, y, IncMinutes));
17698  }
17699  else if(ActionVector.at(z - 1).ArrivalTime != TDateTime(-1))
17700  {
17701  TimeStr = Utilities->Format96HHMM(GetRepeatTime(31, ActionVector.at(z - 1).ArrivalTime, y, IncMinutes));
17702  }
17703  else
17704  {
17705  TimeStr = " "; // shouldn't ever get here
17706  }
17707  }
17708  }
17709  else if(ActionVectorEntry.Command == "Fer")
17710  {
17711  AnsiString AllowedExits;
17712  PartStr = "Exits railway" + GetExitLocationAndAt(0, ActionVectorEntry.ExitList, AllowedExits) + AllowedExits;
17713  TimeStr = Utilities->Format96HHMM(GetRepeatTime(18, ActionVectorEntry.EventTime, y, IncMinutes));
17714  }
17715  else if(ActionVectorEntry.Command == "Fjo")
17716  {
17717  PartStr = "At " + ActionVectorEntry.LocationName + " joins";
17718  TimeStr = GetRepeatHeadCode(44, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
17719  Utilities->Format96HHMM(GetRepeatTime(19, ActionVectorEntry.EventTime, y, IncMinutes));
17720  }
17721  }
17722  else if(ActionVectorEntry.SequenceType == SequTypeForRepeatEntry)
17723  {
17724  continue; // no entry needed for a repeat
17725  }
17726  OneTTEntry.Action = PartStr;
17727  OneTTEntry.Time = TimeStr;
17728  OneTTTrain.OneFormattedTrainVector.push_back(OneTTEntry);
17729  // one per action
17730  }
17731  OneTTLine.OneCompleteFormattedTrainVector.push_back(OneTTTrain);
17732  // one per repeat
17733  }
17734  AllTTTrains->push_back(OneTTLine); // one per repeating train
17735  }
17736  // AllTTTrains vector now complete
17737 
17738  std::ofstream TTFile(TTFileName.c_str()); //formatted timetable
17739 
17740  if(TTFile == 0)
17741  {
17742  StopTTClockMessage(64, "Formatted timetable file failed to open - can't be created");
17743  delete AllTTTrains;
17744  Utilities->CallLogPop(1567);
17745  return;
17746  }
17747 /* formatted timetable types
17748  class TOneTrainFormattedEntry
17749  {
17750  AnsiString Action;//includes location if relevant
17751  AnsiString Time;
17752  };
17753 
17754  typedef std::vector<TOneTrainFormattedEntry> TOneFormattedTrainVector;
17755 
17756  class TOneCompleteFormattedTrain//headcode + list of actions
17757  {
17758  public:
17759  AnsiString HeadCode;
17760  TOneFormattedTrainVector OneFormattedTrainVector;
17761  };
17762 
17763  typedef std::vector<TOneCompleteFormattedTrain> TOneCompleteFormattedTrainVector;//list af all repeats
17764 
17765  class TTrainFormattedInformation//contains all information for a single TT entry (including repeats)
17766  {
17767  public:
17768  AnsiString Header;//description, mass, power, brake rate etc
17769  int NumberOfTrains;// number of repeats + 1
17770  TOneCompleteFormattedTrainVector OneCompleteFormattedTrainVector;//list af all repeats
17771  };
17772 
17773  typedef std::vector<TTrainFormattedInformation> TAllFormattedTrains;//all timetable in formatted form
17774  //end of formatted timetable types
17775 */
17776 
17777  // new layout using multiple rows
17778  TTFile << TableTitle.c_str() << '\n' << '\n';
17779  for(unsigned int x = 0; x < AllTTTrains->size(); x++)
17780  {
17781  TTFile << AllTTTrains->at(x).Header.c_str();
17782  TTFile << '\n';
17783  TTFile << ','; // for the blank line above the action list
17784  for(int y = 0; y < AllTTTrains->at(x).NumberOfTrains; y++) // number of repeating trains
17785  {
17786  if(y < (AllTTTrains->at(x).NumberOfTrains - 1))
17787  {
17788  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).HeadCode.c_str() << ',';
17789  }
17790  else
17791  {
17792  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).HeadCode.c_str();
17793  }
17794  }
17795  TTFile << '\n' << '\n';
17796 
17797  for(unsigned int z = 0; z < AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(0).OneFormattedTrainVector.size(); z++)
17798  {
17799  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(0).OneFormattedTrainVector.at(z).Action.c_str() << ',';
17800  for(int y = 0; y < AllTTTrains->at(x).NumberOfTrains; y++) // number of repeating trains
17801  {
17802  if(y < (AllTTTrains->at(x).NumberOfTrains - 1))
17803  {
17804  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.at(z).Time.c_str() << ',';
17805  }
17806  else
17807  {
17808  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.at(z).Time.c_str();
17809  }
17810  }
17811  TTFile << '\n';
17812  }
17813  TTFile << '\n' << '\n';
17814  }
17815 
17816  TTFile.close();
17817 
17818  AnsiString TTFileName2 = TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss");
17819 
17820  TTFileName2 = CurDir + "\\Formatted timetables\\Timetable " + TTFileName2 + "; " + RailwayTitle + "; " + TimetableTitle + ".txt";
17821 
17822  std::ofstream TTFile2(TTFileName2.c_str()); //chronological timetable
17823 
17824  if(TTFile2 == 0)
17825  {
17826  StopTTClockMessage(67, "Chronological timetable file failed to open - can't be created");
17827  delete AllTTTrains;
17828  Utilities->CallLogPop(1710);
17829  return;
17830  }
17831  typedef std::multimap<AnsiString, AnsiString>TAnsiMultiMap;
17832  std::multimap<AnsiString, AnsiString>::iterator AMMIT;
17833  std::pair<AnsiString, AnsiString>AnsiMultiMapEntry;
17834 
17835  TAnsiMultiMap *TAMM = new TAnsiMultiMap;
17836  LastTTTime = ""; //records the very last time in the timetable - used in analysis file for Frh entries
17837 
17838  // multimap of AnsiStrings with TimeString as key (to sort automatically)
17839 
17840  TTFile2 << TableTitle.c_str() << '\n' << '\n';
17841  for(unsigned int x = 0; x < AllTTTrains->size(); x++)
17842  {
17843  for(int y = 0; y < AllTTTrains->at(x).NumberOfTrains; y++) // number of repeating trains
17844  {
17845  for(unsigned int z = 0; z < AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.size(); z++)
17846  {
17847  bool GiveMessagesFalse = false;
17848  AnsiString TimeString = AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.at(z).Time;
17849  AnsiString HeadCodeString = AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).HeadCode;
17850  AnsiString ActionString = AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.at(z).Action;
17851  if(CheckHeadCodeValidity(11, GiveMessagesFalse, TimeString.SubString(1, 4)))
17852  // 'NXNN at HH:MM' (will return true if H/C as integ check passed)
17853  {
17854  // fails for HH:MM because of ':' or 'End at HH:MM' because of ' '
17855  AnsiString OtherHeadCode = TimeString.SubString(1, 4);
17856  TimeString = TimeString.SubString(9, 5);
17857  ActionString += " " + OtherHeadCode;
17858  }
17859  if(TimeString.SubString(1, 7) == "End at ")
17860  // for Frh-sh final entry
17861  {
17862  TimeString = TimeString.SubString(8, 5);
17863  }
17864  AnsiString OneLine = TimeString + ' ' + HeadCodeString + ' ' + ActionString + '\n';
17865  AnsiMultiMapEntry.first = TimeString;
17866  AnsiMultiMapEntry.second = OneLine;
17867  TAMM->insert(AnsiMultiMapEntry);
17868  }
17869  }
17870  }
17871 
17872  for(AMMIT = TAMM->begin(); AMMIT != TAMM->end(); AMMIT++)
17873  {
17874  TTFile2 << (AMMIT->second).c_str();
17875  }
17876  delete AllTTTrains;
17877  delete TAMM;
17878  TTFile2.close();
17879  Utilities->CallLogPop(1580);
17880 }
17881 
17882 // ---------------------------------------------------------------------------
17883 
17884 bool TTrainController::CreateTTAnalysisFile(int Caller, AnsiString RailwayTitle, AnsiString TimetableTitle, AnsiString CurDir, bool ArrChecked, bool DepChecked,
17885  bool AtLocChecked, bool DirChecked, int ArrRange, int DepRange)
17886 {
17887 
17888  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CreateTTAnalysisFile");
17889  bool AnalysisError = false;
17890  AnsiString SequenceLog = "SequenceLog\n";
17891 
17892 /* Double crosslink (shuttle) table:
17893 
17894 Command Format OtherHead NonRepeating- LinkTrain- NonRepeating- Decsription
17895  Code ShuttleLink- EntryPtr ShuttleLink-
17896  HeadCode EntryPtr
17897 
17898 Snt-sh SNTShuttle Y (rtn shuttle) N Y (rtn sh) N Simple shuttle - no feeder service
17899 Frh-sh TimeCmdHeadCode Y (outwd shuttle) N Y (outwd sh) N Simple shuttle - no finishing service
17900 F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
17901 Sns-sh SNSShuttle Y (rtn shuttle) Y (feeder) Y (rtn) Y (fdr) Shuttle link from feeder service
17902 Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
17903 Fns-sh FSHNewService Y (outwd shuttle) Y (finishing) Y (outwd sh) Y (finish) Shuttle link to finishing service
17904 
17905 Note: Any shuttle start can have any finish - feeder and finish, neither, feeder but no finish & vice versa.
17906 */
17907 
17908  try
17909  {
17910  //New section at v2.5.0 for tt conflict analysis
17911  /*
17912  typedef std::list<AnsiString> TServiceCallingLocsList;
17913  typedef std::map<AnsiString, TServiceCallingLocsList> TAllServiceCallingLocsMap;
17914 
17916  struct TLocServiceTimes
17917  {
17918  AnsiString Location;
17919  AnsiString ServiceAndRepeatNum;
17920  AnsiString AtLocTime;
17921  AnsiString ArrTime;
17922  AnsiString DepTime;
17923  AnsiString FrhMarker;
17924  };
17925  typedef std::vector<TLocServiceTimes> TLocServiceTimesVector;
17926  */
17927 
17928  //first have to check through all the services and give each one a unique name, or the analysis won't recognise differences between services that have the same reference
17929  //to do that need a new TrainDataVector as don't want to change anything in the original. TrainDataVectorCopy is used for building AllServiceCallingLocsMap & LocServiceTimesVector
17930 
17931 //create TrainDataVectorCopy and populate service refs with /1, /2 etc
17932  TrainDataVectorCopy = TrainDataVector; //don't need it on heap as TrainController is on the heap. Didn't need others in CreatFormattedTimetables but leave as is.
17933  TTrainDataVector::iterator TDVIt, TDVCopyIt;
17934  int Suffix = 0;
17935  int IteratorNumber = 0;
17936  AnsiString AnsiSuffix = "";
17937  for(TDVIt = TrainDataVector.begin(); TDVIt != TrainDataVector.end() - 1; TDVIt++)
17938  {
17939  IteratorNumber++; //first value in loop is 1
17940  Suffix = 0;
17941  for(TDVCopyIt = TrainDataVectorCopy.begin() + IteratorNumber; TDVCopyIt != TrainDataVectorCopy.end(); TDVCopyIt++)
17942  {
17943  if(TDVCopyIt->HeadCode == TDVIt->HeadCode)
17944  {
17945  Suffix++; //first value is 1
17946  AnsiSuffix = AnsiString(Suffix);
17947  TDVCopyIt->ServiceReference = TDVIt->HeadCode + "/" + AnsiSuffix; //set both the HeadCode + any forward slashes and numbers, this is because sometimes
17948  TDVCopyIt->HeadCode = TDVIt->HeadCode + "/" + AnsiSuffix; //service refs are used and sometimes H/Cs, so need them to be the same,
17949  } //they are all unique at this point anyway
17950  }
17951  }
17952 //now make all linked pointers in ActionVectorEntries point to links in the vector copy (still point to original vector at this stage)
17953 //and set the linked headcodes to the correct values - all unique at this point
17954  int Increment = 0, SlashPos;
17955  TActionVectorIterator AVEIt;
17956  AnsiString LinkedHeadCode;
17957 
17958  for(TDVCopyIt = TrainDataVectorCopy.begin(); TDVCopyIt != TrainDataVectorCopy.end(); TDVCopyIt++)
17959  {
17960  for(AVEIt = TDVCopyIt->ActionVector.begin(); AVEIt != TDVCopyIt->ActionVector.end(); AVEIt++)
17961  {
17962  if(AVEIt->LinkedTrainEntryPtr != NULL)
17963  {
17964  Increment = AVEIt->LinkedTrainEntryPtr - &TrainDataVector.at(0);
17965  AVEIt->LinkedTrainEntryPtr = &TrainDataVectorCopy.at(0) + Increment;
17966  //now set AVEIt->OtherHeadCode to the linked headcodes with /1, /2 etc
17967  //but note that these ARE HeadCodes and not service refs, so need to strip off any prefixes from the linked service refs
17968  LinkedHeadCode = (*AVEIt->LinkedTrainEntryPtr).ServiceReference;
17969  //now count from back until reach a '/' character or a non-integer character, if reach non-integer first then no '/' present at end of H/c (but may be one earlier as a prefix)
17970  SlashPos = 0;
17971  for(int x = LinkedHeadCode.Length(); x > 0; x--)
17972  {
17973  if(LinkedHeadCode[x] == '/')
17974  {
17975  SlashPos = LinkedHeadCode.Length() - x + 1;
17976  break;
17977  }
17978  else if((LinkedHeadCode[x] != '0') && (LinkedHeadCode[x] != '1') && (LinkedHeadCode[x] != '2') && (LinkedHeadCode[x] != '3') &&
17979  (LinkedHeadCode[x] != '4') && (LinkedHeadCode[x] != '5') && (LinkedHeadCode[x] != '6') && (LinkedHeadCode[x] != '7') &&
17980  (LinkedHeadCode[x] != '8') && (LinkedHeadCode[x] != '8'))
17981  {
17982  break;
17983  }
17984  }
17985  //now strip off any prefix
17986  AVEIt->OtherHeadCode = LinkedHeadCode.SubString(LinkedHeadCode.Length() - 3 - SlashPos, 4 + SlashPos);
17987  }
17988  else
17989  {
17990  AVEIt->OtherHeadCode = "";
17991  }
17992  if(AVEIt->NonRepeatingShuttleLinkEntryPtr != NULL)
17993  {
17994  Increment = AVEIt->NonRepeatingShuttleLinkEntryPtr - &TrainDataVector.at(0);
17995  AVEIt->NonRepeatingShuttleLinkEntryPtr = &TrainDataVectorCopy.at(0) + Increment;
17996  //now set AVEIt->NonRepeatingShuttleLinkHeadCode to the linked headcodes with /1, /2 etc
17997  //but note that these ARE HeadCodes and not service refs, so need to strip off any prefixes from the linked service refs
17998  LinkedHeadCode = (*AVEIt->NonRepeatingShuttleLinkEntryPtr).ServiceReference;
17999  //now count from back until reach a '/' character or a non-integer character, if reach non-integer first then no '/' present at end of H/c (but may be one earlier as a prefix)
18000  SlashPos = 0;
18001  for(int x = LinkedHeadCode.Length(); x > 0; x--)
18002  {
18003  if(LinkedHeadCode[x] == '/')
18004  {
18005  SlashPos = LinkedHeadCode.Length() - x + 1;
18006  break;
18007  }
18008  else if((LinkedHeadCode[x] != '0') && (LinkedHeadCode[x] != '1') && (LinkedHeadCode[x] != '2') && (LinkedHeadCode[x] != '3') &&
18009  (LinkedHeadCode[x] != '4') && (LinkedHeadCode[x] != '5') && (LinkedHeadCode[x] != '6') && (LinkedHeadCode[x] != '7') &&
18010  (LinkedHeadCode[x] != '8') && (LinkedHeadCode[x] != '8'))
18011  {
18012  break;
18013  }
18014  }
18015  //now strip off any prefix
18016  AVEIt->NonRepeatingShuttleLinkHeadCode = LinkedHeadCode.SubString(LinkedHeadCode.Length() - 3 - SlashPos, 4 + SlashPos);
18017  }
18018  else
18019  {
18020  AVEIt->NonRepeatingShuttleLinkHeadCode = "";
18021  }
18022  }
18023  }
18024  //from here only TrainDataVectorCopy used
18025  SequenceLog += "1\n";
18026  //build AllServiceCallingLocsMap, it only uses the base service reference (with /1, /2 etc suffixes) as later times are calculated from the repeat number
18027  TServiceCallingLocsList ServiceCallingLocsList;
18028  std::pair<AnsiString, TServiceCallingLocsList> AllServiceCallingLocsEntry;
18029  for(unsigned int x = 0; x < TrainDataVectorCopy.size(); x++)
18030  {
18031  const TTrainDataEntry &TrainDataEntry = TrainDataVectorCopy.at(x);
18032  const TActionVector &ActionVector = TrainDataEntry.ActionVector;
18033  AllServiceCallingLocsEntry.first = TrainDataEntry.ServiceReference;
18034  ServiceCallingLocsList.clear();
18035  if(ActionVector.empty())
18036  {
18037  continue;
18038  }
18039  if(ActionVector.at(0).SignallerControl)
18040  {
18041  continue;
18042  }
18043  for(unsigned int z = 0; z < ActionVector.size(); z++)
18044  {
18045  TActionVectorEntry AVE = ActionVector.at(z);
18046  if(AVE.FormatType == StartNew)
18047  {
18048  if(AVE.LocationType == AtLocation) //located Snt
18049  {
18050  ServiceCallingLocsList.push_back(AVE.LocationName);
18051  }
18052  else //unlocated Snt (could be entering at continuation)
18053  {
18055  if(TE.ActiveTrackElementName != "")
18056  {
18057  ServiceCallingLocsList.push_back(TE.ActiveTrackElementName);
18058  }
18059  else
18060  {
18061  int HLoc = TE.HLoc;
18062  int VLoc = TE.VLoc;
18063  AnsiString HString;
18064  AnsiString VString;
18065  if(HLoc < 0)
18066  {
18067  HString = AnsiString('N') + AnsiString(HLoc).SubString(2, AnsiString(HLoc).Length() - 1); //strip off '-'
18068  }
18069  else
18070  {
18071  HString = AnsiString(HLoc);
18072  }
18073  if(VLoc < 0)
18074  {
18075  VString = AnsiString('N') + AnsiString(VLoc).SubString(2, AnsiString(VLoc).Length() - 1); //strip off '-'
18076  }
18077  else
18078  {
18079  VString = AnsiString(VLoc);
18080  }
18081  ServiceCallingLocsList.push_back(HString + '-' + VString);
18082  }
18083  }
18084  }
18085  else if(AVE.SequenceType == StartSequence) //other start entries, all located
18086  {
18087  ServiceCallingLocsList.push_back(AVE.LocationName);
18088  }
18089  else if(AVE.FormatType == TimeLoc) //z must be > 0
18090  {
18091  if(ServiceCallingLocsList.back() != AVE.LocationName)
18092  {
18093  ServiceCallingLocsList.push_back(AVE.LocationName); //may be listed twice in succession so only want one entry
18094  }
18095  }
18096  else if(AVE.FormatType == PassTime)
18097  {
18098  ServiceCallingLocsList.push_back(AVE.LocationName);
18099  }
18100  else if(AVE.FormatType == TimeTimeLoc)
18101  {
18102  ServiceCallingLocsList.push_back(AVE.LocationName);
18103  }
18104  else if(AVE.Command == "cdt") //list if not next to start or finish
18105  {
18106  if(ActionVector.at(z-1).SequenceType == StartSequence)
18107  {
18108  continue;
18109  }
18110  else if(ActionVector.at(z+1).SequenceType == FinishSequence) //although deal with Fer entries cdt (train stopped) can't precede FER (train moving)
18111  {
18112  continue;
18113  }
18114  else
18115  {
18116  AnsiString TimeString = Utilities->Format96HHMM(AVE.EventTime);
18117  ServiceCallingLocsList.push_back("%%%" + TimeString); //%%% is a marker - unlikely that any locations will begin with this & easy to check to identify a time
18118  }
18119  }
18120  else if(AVE.FormatType == ExitRailway) //Fer
18121  {
18122  TTrackElement TE = Track->TrackElementAt(995, AVE.ExitList.front());
18123  AnsiString LName = TE.ActiveTrackElementName;
18124  if(LName != "")
18125  {
18126  ServiceCallingLocsList.push_back(LName);
18127  }
18128  else
18129  {
18130  int HLoc = TE.HLoc;
18131  int VLoc = TE.VLoc;
18132  AnsiString HString;
18133  AnsiString VString;
18134  if(HLoc < 0)
18135  {
18136  HString = AnsiString('N') + AnsiString(HLoc).SubString(2, AnsiString(HLoc).Length() - 1); //strip off '-'
18137  }
18138  else
18139  {
18140  HString = AnsiString(HLoc);
18141  }
18142  if(VLoc < 0)
18143  {
18144  VString = AnsiString('N') + AnsiString(VLoc).SubString(2, AnsiString(VLoc).Length() - 1); //strip off '-'
18145  }
18146  else
18147  {
18148  VString = AnsiString(VLoc);
18149  }
18150  ServiceCallingLocsList.push_back(HString + '-' + VString);
18151  }
18152  }
18153  }
18154  AllServiceCallingLocsEntry.second = ServiceCallingLocsList;
18155  AllServiceCallingLocsMap.insert(AllServiceCallingLocsEntry);
18156  }
18157  //AllServiceCallingLocsMap built
18158  SequenceLog += "2\n";
18159 /*
18160 // this sequence is to test the validity of AllServiceCallingLocsMap
18161  AnsiString TestFile = CurDir + "\\Formatted timetables\\TestFile; " + RailwayTitle + "; " + TimetableTitle + ".txt";
18162  std::ofstream Test(TestFile.c_str());
18163 
18164  if(TestFile == 0)
18165  {
18166  ShowMessage("TestFile failed to open - can't be created");
18167  Utilities->CallLogPop();
18168  return false;
18169  }
18170 
18171  for(TAllServiceCallingLocsMap::iterator ASCLIt = AllServiceCallingLocsMap.begin(); ASCLIt != AllServiceCallingLocsMap.end(); ASCLIt++)
18172  {
18173  Test << ASCLIt->first << '\n'; //service ref
18174  for(TServiceCallingLocsList::iterator SCLIt = ASCLIt->second.begin(); SCLIt != ASCLIt->second.end(); SCLIt++)
18175  {
18176  Test << *SCLIt << '\n';
18177  }
18178  Test << "\n\n";
18179  }
18180  Test.close();
18181  Utilities->CallLogPop();
18182  return true;
18183 */
18184  //initialise variables before calc LastTTTime & build LocServiceTimesVector
18185  if(TrainDataVectorCopy.empty())
18186  {
18187  ShowMessage("Unable to create a program-readable timetable - please check the timetable file validity");
18188  Utilities->CallLogPop(2209);
18189  return(false);
18190  }
18191  TLocServiceTimes TLSTEntry;
18192  TLocServiceTimesVector LocServiceTimesVector; //will be on heap as TrainController is on the heap
18193  bool NumPlatsAtThisLocCalculated = false, ArrivalsPrinted = false, DeparturesPrinted = false, AtLocsPrinted = false;
18194  AnsiString PreviousService = "", PreviousServiceAndRepeatNumTotalOutput = "", BasicTime = "", MinuteString = "", LastAnsiTime = "";
18195  int NumTrains = 0, NumPlats = 0, LastFrhCount = 0, FrhCount = 0, NumTrainsAtLoc = 0;
18196  LastTTTime = "";
18197  SequenceLog += "3\n";
18198  //calculate LastTTTime
18199  for(unsigned int x = 0; x < TrainDataVectorCopy.size(); x++)
18200  {
18201  TTrainDataEntry &TrainDataEntry = TrainDataVectorCopy.at(x);
18202  TActionVector &ActionVector = TrainDataEntry.ActionVector;
18203  TActionVectorIterator AVLast = ActionVector.end() - 1; //points to last entry
18204  TDateTime LastTDTime;
18205  int IncMinutes = 0;
18206  NumTrains = TrainDataEntry.NumberOfTrains;
18207  if(ActionVector.empty())
18208  {
18209  continue;
18210  }
18211  if(ActionVector.at(0).SignallerControl)
18212  {
18213  continue;
18214  }
18215  if(AVLast->FormatType == Repeat)
18216  {
18217  IncMinutes = ActionVector.at(ActionVector.size() - 1).RearStartOrRepeatMins;
18218  AVLast--; //now points to the command before the repeat
18219  }
18220  if(AVLast->FormatType == FinRemHere) //not 'else if' as may have both a repeat and an Frh
18221  {
18222  AVLast--; //points to last timed entry
18223  }
18224  //here AVLast points to last entry with a time
18225  if(AVLast->ArrivalTime != TDateTime(-1))
18226  {
18227  LastTDTime = AVLast->ArrivalTime;
18228  }
18229  else if(AVLast->EventTime != TDateTime(-1)) //can't be a departure time
18230  {
18231  LastTDTime = AVLast->EventTime;
18232  }
18233  else
18234  {
18235  continue; //shouldn't ever reach here but if do then skip this service
18236  }
18237  if(NumTrains == 1)
18238  {
18239  LastAnsiTime = Utilities->Format96HHMM(LastTDTime);
18240  }
18241  else
18242  {
18243  LastAnsiTime = Utilities->Format96HHMM(GetRepeatTime(59, LastTDTime, NumTrains - 1, IncMinutes));
18244  }
18245  if(LastAnsiTime > LastTTTime)
18246  {
18247  LastTTTime = LastAnsiTime;
18248  }
18249  }
18250  SequenceLog += "4\n";
18251 //build LocServiceTimesVector
18252 
18253 /*
18254  struct TLocServiceTimes
18255  {
18256  AnsiString Location;
18257  AnsiString ServiceAndRepeatNum;
18258  AnsiString AtLocTime;
18259  AnsiString ArrTime;
18260  AnsiString DepTime;
18261  AnsiString FrhMarker;
18262  };
18263  typedef std::vector<TLocServiceTimes> TLocServiceTimesVector;
18264 
18265 This works as follows:
18266 ServiceAndRepeatNum is taken from the TrainDataVectorCopy as it is the same for all actionvector entries
18267 Location is taken from ActionVectorEntry.LocationName if there is one, or from the H & V locations if not (e.g. at an unnamed Fer)
18268 AtLocTime is always entered either on its own or with ArrTime or DepTime as appropriate
18269 
18270 Every action for every train is examined and times entered as follows:-
18271 a) a located Snt: entry time becomes the AtLocTime, and all subsequent minutes entered too up to but not including a departure or a finish
18272 b) an unlocated Snt: entry time becomes DepTime
18273 c) all other start entries: entry time becomes AtLoc, and all subsequent minutes entered too up to but not including a departure or a finish
18274 d) TimeLoc Arr: entry time becomes ArrTime, and all subsequent minutes entered too up to but not including a departure or a finish
18275 e) TimeLoc Dep: entry time becomes DepTime, checks if DepTime same as earlier ArrTime and if so all times go in as one entry
18276 f) TimeTimeLoc: Arrival time entered as ArrTime, a check if Arr & Dep same and if so go in as one entry, else all minutes between entered as AtLocs then DepTime
18277 g) ExitRailway (Fer): check if located and use LocationName if so. else use H & V positions, time becomes AtLocTime
18278 h) Frh: use the earlier vector time as the AtLocTime and set FrhMarker, and enter all minutes to end of timetable as AtLocs
18279 i) Frh-sh: for the last train use time as AtLocTime, set FrhMarker, and enter all minutes to end of timetable as AtLocs
18280 j) all other finish entries (all link to another service) are ignored as will be listed for the linked service
18281 */
18282  for(unsigned int x = 0; x < TrainDataVectorCopy.size(); x++)
18283  {
18284  const TTrainDataEntry &TrainDataEntry = TrainDataVectorCopy.at(x);
18285  const TActionVector &ActionVector = TrainDataEntry.ActionVector;
18286  AnsiString ServiceRef = TrainDataEntry.ServiceReference;
18287  int IncMinutes = 0;
18288  NumTrains = TrainDataEntry.NumberOfTrains;
18289  if(ActionVector.empty())
18290  {
18291  continue;
18292  }
18293  if(ActionVector.at(0).SignallerControl)
18294  {
18295  continue;
18296  }
18297  if(ActionVector.at(ActionVector.size() - 1).FormatType == Repeat)
18298  {
18299  IncMinutes = ActionVector.at(ActionVector.size() - 1).RearStartOrRepeatMins;
18300  }
18301  for(int y = 0; y < NumTrains; y++) //y is the repeat number
18302  {
18303  if(NumTrains == 1)
18304  {
18305  TLSTEntry.ServiceAndRepeatNum = ServiceRef;
18306  }
18307  else if(y == 0)
18308  {
18309  TLSTEntry.ServiceAndRepeatNum = ServiceRef + " (First service)";
18310  }
18311  else
18312  {
18313  TLSTEntry.ServiceAndRepeatNum = ServiceRef + " (Repeat " + AnsiString(y) + ")";
18314  }
18315  for(unsigned int z = 0; z < ActionVector.size(); z++)
18316  {
18317  TActionVectorEntry AVE = ActionVector.at(z);
18318  TLSTEntry.AtLocTime = "";
18319  TLSTEntry.ArrTime = "";
18320  TLSTEntry.DepTime = "";
18321  TLSTEntry.Location = "";
18322  TLSTEntry.FrhMarker = "";
18323 
18324  if(AVE.FormatType == StartNew) //Snt only
18325  {
18326  if(AVE.LocationType == AtLocation) //located Snt, class time as AtLocTime
18327  {
18328  TLSTEntry.Location = AVE.LocationName;
18329  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(58, AVE.EventTime, y, IncMinutes));
18330  LocServiceTimesVector.push_back(TLSTEntry);
18331 
18332  //now look forwards until find a departure or a Fns, Fns-sh etc & add in all the minutes up to but not including the dep or finish times
18333  AnsiString IncTime = "", FoundStopTime = ""; //these handled in later checks
18334  for(unsigned int a = z + 1; a < ActionVector.size(); a++)
18335  {
18336  if(ActionVector.at(a).FormatType == TimeLoc) //must be a departure
18337  {
18338  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(62, ActionVector.at(a).DepartureTime, y, IncMinutes));
18339  break;
18340  }
18341  if(ActionVector.at(a).SequenceType == FinishSequence) //finish catered in a later test
18342  {
18343  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(63, ActionVector.at(a).EventTime, y, IncMinutes));
18344  break;
18345  }
18346  }
18347  if(FoundStopTime == "")
18348  {
18349  throw Exception("Failure to determine FoundStopTime for located Snt");
18350  }
18351  int WhileCount = 0;
18352  while(true)
18353  {
18354  //add minutes until reach FoundStopTime but don't add that time
18355  WhileCount++;
18356  IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
18357  TLSTEntry.AtLocTime = IncTime; //all entered times will be AtLocs
18358  TLSTEntry.DepTime = "";
18359  TLSTEntry.ArrTime = "";
18360  if(IncTime >= FoundStopTime) //don't add that time
18361  {
18362  break;
18363  }
18364  LocServiceTimesVector.push_back(TLSTEntry);
18365  if(WhileCount > 2000)
18366  {
18367  throw Exception("While loop failed to break in 2000 loops for located Snt");
18368  }
18369  }
18370  }
18371  else //unlocated Snt, use the EventTime as DepTime for this vector
18372  {
18374  if(TE.ActiveTrackElementName != "")
18375  {
18376  TLSTEntry.Location = TE.ActiveTrackElementName;
18377  }
18378  else
18379  {
18380  int HLoc = TE.HLoc;
18381  int VLoc = TE.VLoc;
18382  AnsiString HString;
18383  AnsiString VString;
18384  if(HLoc < 0)
18385  {
18386  HString = AnsiString('N') + AnsiString(HLoc).SubString(2, AnsiString(HLoc).Length() - 1); //strip off '-'
18387  }
18388  else
18389  {
18390  HString = AnsiString(HLoc);
18391  }
18392  if(VLoc < 0)
18393  {
18394  VString = AnsiString('N') + AnsiString(VLoc).SubString(2, AnsiString(VLoc).Length() - 1); //strip off '-'
18395  }
18396  else
18397  {
18398  VString = AnsiString(VLoc);
18399  }
18400  TLSTEntry.Location = HString + '-' + VString;
18401  }
18402  TLSTEntry.DepTime = Utilities->Format96HHMM(GetRepeatTime(49, AVE.EventTime, y, IncMinutes));
18403  TLSTEntry.AtLocTime = TLSTEntry.DepTime;
18404  LocServiceTimesVector.push_back(TLSTEntry);
18405  }
18406  }
18407 
18408  else if(AVE.SequenceType == StartSequence) //other start entries, all located
18409  {
18410  TLSTEntry.Location = AVE.LocationName;
18411  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(50, AVE.EventTime, y, IncMinutes));
18412  LocServiceTimesVector.push_back(TLSTEntry);
18413  //now look forwards until find a departure or a Fns, Fns-sh etc & add in all the minutes up to but not including the dep or finish times
18414  AnsiString IncTime = "", FoundStopTime = ""; //these handled in other checks
18415  for(unsigned int a = z + 1; a < ActionVector.size(); a++)
18416  {
18417  if(ActionVector.at(a).FormatType == TimeLoc) //must be a departure
18418  {
18419  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(64, ActionVector.at(a).DepartureTime, y, IncMinutes));
18420  break;
18421  }
18422  if(ActionVector.at(a).SequenceType == FinishSequence) //finish catered in a later test
18423  {
18424  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(65, ActionVector.at(a).EventTime, y, IncMinutes));
18425  break;
18426  }
18427  }
18428  if(FoundStopTime == "")
18429  {
18430  throw Exception("Failure to determine FoundStopTime for SequenceType == StartSequence");
18431  }
18432  int WhileCount = 0;
18433  while(true)
18434  {
18435  //add minutes until reach FoundStopTime but don't add that time
18436  WhileCount++;
18437  IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
18438  TLSTEntry.AtLocTime = IncTime; //all entered times will be AtLocs
18439  TLSTEntry.DepTime = "";
18440  TLSTEntry.ArrTime = "";
18441  if(IncTime >= FoundStopTime) //don't add that time
18442  {
18443  break;
18444  }
18445  LocServiceTimesVector.push_back(TLSTEntry);
18446  if(WhileCount > 2000)
18447  {
18448  throw Exception("While loop failed to break in 2000 loops for SequenceType == StartSequence");
18449  }
18450  }
18451  }
18452 
18453  else if(AVE.FormatType == TimeLoc) //could be arr or dep, if arrival add in all mins to the departure or finish
18454  {
18455  TLSTEntry.Location = AVE.LocationName;
18456  if(AVE.ArrivalTime > TDateTime(-1)) //one or other set, not both, in this case arrival
18457  {
18458  bool SkipAddingMinutes = false;
18459  TLSTEntry.ArrTime = Utilities->Format96HHMM(GetRepeatTime(51, AVE.ArrivalTime, y, IncMinutes));
18460  TLSTEntry.AtLocTime = TLSTEntry.ArrTime;
18461  LocServiceTimesVector.push_back(TLSTEntry); //Arr and AtLoc added (may be popped if dep time found to be same at next TimeLoc)
18462  //now look forwards until find a departure or a Fns, Fns-sh etc & add in all the minutes up to but not including the dep or finish times
18463  AnsiString IncTime = "", FoundStopTime = ""; //these handled in other checks
18464  for(unsigned int a = z + 1; a < ActionVector.size(); a++)
18465  {
18466  if(ActionVector.at(a).FormatType == TimeLoc) //must be a departure
18467  {
18468  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(66, ActionVector.at(a).DepartureTime, y, IncMinutes));
18469  break;
18470  }
18471  if(ActionVector.at(a).SequenceType == FinishSequence) //finish catered for in a later test
18472  {
18473  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(67, ActionVector.at(a).EventTime, y, IncMinutes));
18474  if((a <= (z + 2)) && (FoundStopTime == TLSTEntry.ArrTime) && ((ActionVector.at(a).LinkedTrainEntryPtr != NULL) || (ActionVector.at(a).NonRepeatingShuttleLinkEntryPtr != NULL)))
18475  //finish immediately after arrival at same time, and a forward linked service. Added at v2.6.0 to prevent two linked trains being listed at same location
18476  //at v2.10.0 changed (a == z + 1) to (a <= (z + 2)) as can have a cdt between, this allows for that
18477  {
18478  LocServiceTimesVector.pop_back(); //pop the entry as the linked train will be listed at the relevant time and don't want to list both
18479  SkipAddingMinutes = true;
18480  }
18481  break;
18482  }
18483  }
18484  if(FoundStopTime == "")
18485  {
18486  throw Exception("Failure to determine FoundStopTime for SequenceType == StartSequence");
18487  }
18488  if(!SkipAddingMinutes)
18489  {
18490  int WhileCount = 0;
18491  while(true)
18492  {
18493  //add minutes until reach FoundStopTime but don't add that time
18494  WhileCount++;
18495  IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
18496  TLSTEntry.AtLocTime = IncTime; //all entered times will be AtLocs
18497  TLSTEntry.DepTime = "";
18498  TLSTEntry.ArrTime = "";
18499  if(IncTime >= FoundStopTime) //don't add that time
18500  {
18501  break;
18502  }
18503  LocServiceTimesVector.push_back(TLSTEntry);
18504  if(WhileCount > 2000)
18505  {
18506  throw Exception("While loop failed to break in 2000 loops for SequenceType == StartSequence");
18507  }
18508  }
18509  }
18510  }
18511  else if(AVE.DepartureTime > TDateTime(-1)) //need to check if the arrival time (which should already be listed) is same and if so put all times on one line
18512  {
18513  TLSTEntry.DepTime = Utilities->Format96HHMM(GetRepeatTime(52, AVE.DepartureTime, y, IncMinutes));
18514  TLSTEntry.AtLocTime = TLSTEntry.DepTime;
18515  if((TLSTEntry.Location == LocServiceTimesVector.back().Location) && (TLSTEntry.ServiceAndRepeatNum == LocServiceTimesVector.back().ServiceAndRepeatNum)) //if not it's a new service
18516  {
18517  if(TLSTEntry.DepTime == LocServiceTimesVector.back().ArrTime)
18518  {
18519  TLSTEntry.ArrTime = LocServiceTimesVector.back().ArrTime;
18520  LocServiceTimesVector.pop_back();
18521  LocServiceTimesVector.push_back(TLSTEntry); //Arr, Dep and AtLoc added in place of earlier Arr entry.
18522  }
18523  else //just add the dep & atloc times
18524  {
18525  TLSTEntry.ArrTime = "";
18526  LocServiceTimesVector.push_back(TLSTEntry);
18527  }
18528  }
18529  else //just add the dep & atloc times
18530  {
18531  TLSTEntry.ArrTime = "";
18532  LocServiceTimesVector.push_back(TLSTEntry);
18533  }
18534  }
18535  }
18536 
18537  else if(AVE.FormatType == TimeTimeLoc)
18538  {
18539  TLSTEntry.Location = AVE.LocationName;
18540  if(AVE.ArrivalTime > TDateTime(-1)) //should be
18541  {
18542  TLSTEntry.ArrTime = Utilities->Format96HHMM(GetRepeatTime(53, AVE.ArrivalTime, y, IncMinutes));
18543  TLSTEntry.AtLocTime = TLSTEntry.ArrTime;
18544  }
18545  if(AVE.DepartureTime > TDateTime(-1)) //should be
18546  {
18547  TLSTEntry.DepTime = Utilities->Format96HHMM(GetRepeatTime(54, AVE.DepartureTime, y, IncMinutes));
18548  }
18549  if(TLSTEntry.ArrTime == TLSTEntry.DepTime)
18550  {
18551  LocServiceTimesVector.push_back(TLSTEntry);
18552  }
18553  else
18554  {
18555  AnsiString TempDepTime = TLSTEntry.DepTime; //save it temporarily
18556  TLSTEntry.DepTime = "";
18557  LocServiceTimesVector.push_back(TLSTEntry); //push just the arrival and AtLoc times
18558  TLSTEntry.ArrTime = ""; //done with this now
18559  while(TLSTEntry.AtLocTime < TempDepTime)
18560  {
18561  TLSTEntry.AtLocTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
18562  if(TLSTEntry.AtLocTime == TempDepTime)
18563  {
18564  TLSTEntry.DepTime = TempDepTime; //restore value
18565  LocServiceTimesVector.push_back(TLSTEntry); //push the AtLoc and Dep times - will finish loop after this
18566  }
18567  else
18568  {
18569  LocServiceTimesVector.push_back(TLSTEntry); //push the AtLoc time on its own
18570  }
18571  }
18572  }
18573  }
18574 
18575  else if(AVE.FormatType == PassTime) //added at v2.9.1
18576  { //adds 2 entries, 1st with PassTime as ArrTime and AtLocTime, 2nd with PassTime as AtLocTime & DepTime
18577  TLSTEntry.Location = AVE.LocationName;;
18578  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(73, AVE.EventTime, y, IncMinutes));
18579  TLSTEntry.ArrTime = TLSTEntry.AtLocTime; //DepTime already set to null
18580  LocServiceTimesVector.push_back(TLSTEntry); //1st entry
18581  TLSTEntry.ArrTime = ""; //need to reset this to null
18582  TLSTEntry.DepTime = TLSTEntry.AtLocTime;
18583  LocServiceTimesVector.push_back(TLSTEntry); //2nd entry
18584  }
18585 
18586  else if(AVE.FormatType == ExitRailway) //Fer
18587  {
18588  TLSTEntry.ArrTime = Utilities->Format96HHMM(GetRepeatTime(77, AVE.EventTime, y, IncMinutes)); //need this as arrival time so arrival analysis works
18589  //properly for exits (continuation entries use dep time so ok)
18590  //added at v2.20.3
18591  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(55, AVE.EventTime, y, IncMinutes));
18592  //don't know which exit will be used during operation so use the first in ExitList, if several with different names then will
18593  //be wrong, but can't guess from here & most will have same name
18594  AnsiString LName = Track->TrackElementAt(990, AVE.ExitList.front()).ActiveTrackElementName;
18595  if(LName != "")
18596  {
18597  TLSTEntry.Location = LName;
18598  }
18599  else
18600  {
18601  int HLoc = Track->TrackElementAt(991, AVE.ExitList.front()).HLoc;
18602  int VLoc = Track->TrackElementAt(992, AVE.ExitList.front()).VLoc;
18603  AnsiString HString;
18604  AnsiString VString;
18605  if(HLoc < 0)
18606  {
18607  HString = AnsiString('N') + AnsiString(HLoc).SubString(2, AnsiString(HLoc).Length() - 1); //strip off '-'
18608  }
18609  else
18610  {
18611  HString = AnsiString(HLoc);
18612  }
18613  if(VLoc < 0)
18614  {
18615  VString = AnsiString('N') + AnsiString(VLoc).SubString(2, AnsiString(VLoc).Length() - 1); //strip off '-'
18616  }
18617  else
18618  {
18619  VString = AnsiString(VLoc);
18620  }
18621  TLSTEntry.Location = HString + '-' + VString;
18622  }
18623  LocServiceTimesVector.push_back(TLSTEntry); //just use the exit time as AtLocTime
18624  }
18625 
18626  else if(AVE.FormatType == FinRemHere) //Frh, not Frh-sh, that dealt with next
18627  {
18628  AnsiString FrhTime;
18629  if(ActionVector.at(z - 1).ArrivalTime != TDateTime(-1))
18630  {
18631  FrhTime = Utilities->Format96HHMM(GetRepeatTime(56, ActionVector.at(z - 1).ArrivalTime, y, IncMinutes));
18632  }
18633  else if(ActionVector.at(z - 1).EventTime != TDateTime(-1))
18634  {
18635  FrhTime = Utilities->Format96HHMM(GetRepeatTime(57, ActionVector.at(z - 1).EventTime, y, IncMinutes));
18636  }
18637  TLSTEntry.AtLocTime = FrhTime; //use the last entry time as the first recorded time
18638  TLSTEntry.Location = AVE.LocationName;
18639  AnsiString IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
18640  TLSTEntry.FrhMarker = "Frh";
18641  LocServiceTimesVector.push_back(TLSTEntry);
18642  TLSTEntry.FrhMarker = "";
18643  //add all times from next minute to end of timetable
18644  while(IncTime <= LastTTTime)
18645  {
18646  TLSTEntry.AtLocTime = IncTime;
18647  LocServiceTimesVector.push_back(TLSTEntry);
18648  IncTime = Utilities->IncrementAnsiTimeOneMinute(IncTime);
18649  }
18650  }
18651 
18652  else if(AVE.Command == "Frh-sh") //do nothing if links to other shuttle but treat as Frh when remaining here
18653  {
18654  if(y == NumTrains - 1) //last repeat, it remains here when accessed for the last train
18655  {
18656  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(68, AVE.EventTime, y, IncMinutes));
18657  TLSTEntry.Location = AVE.LocationName;
18658  AnsiString IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
18659  TLSTEntry.FrhMarker = "Frh";
18660  LocServiceTimesVector.push_back(TLSTEntry);
18661  TLSTEntry.FrhMarker = "";
18662  //add all times from next minute to end of timetable
18663  while(IncTime <= LastTTTime)
18664  {
18665  TLSTEntry.AtLocTime = IncTime;
18666  LocServiceTimesVector.push_back(TLSTEntry);
18667  IncTime = Utilities->IncrementAnsiTimeOneMinute(IncTime);
18668  }
18669  }
18670  }
18671 
18672  else if(AVE.SequenceType == FinishSequence) //other finish types - all located & all link to another service
18673  {
18674  //nothing is done here as the entry will be listed at this time under the new service reference
18675  }
18676  }
18677  }
18678  }
18679  SequenceLog += "5\n";
18680  //now sort in location order
18681  std::sort(LocServiceTimesVector.begin(), LocServiceTimesVector.end(), &LocServiceTimesLocationSort); //LocServiceTimesLocationSort is a function pointer
18682  //LocServiceTimesVector now complete & sorted in location order
18683 
18684 /*
18685 //start of debugging section
18686 //create LocServiceTimesVector output file for debugging purposes
18687  AnsiString LSTVTestFile = CurDir + "\\Formatted timetables\\LSTVTestFile; " + RailwayTitle + "; " + TimetableTitle + ".txt";
18688  std::ofstream LSTVFile(LSTVTestFile.c_str());
18689  for(TLocServiceTimesVector::iterator LSTVIt = LocServiceTimesVector.begin(); LSTVIt != LocServiceTimesVector.end(); LSTVIt++)
18690  {
18691  LSTVFile << LSTVIt->Location + '\n';
18692  LSTVFile << LSTVIt->ServiceAndRepeatNum + '\n';
18693  LSTVFile << "AtLocTime = " + LSTVIt->AtLocTime + '\n';
18694  LSTVFile << "ArrTime = " + LSTVIt->ArrTime + '\n';
18695  LSTVFile << "DepTime = " + LSTVIt->DepTime + '\n';
18696  if(LSTVIt->FrhMarker == "")
18697  {
18698  LSTVFile << "Not Frh\n";
18699  }
18700  else
18701  {
18702  LSTVFile << LSTVIt->FrhMarker + '\n';
18703  }
18704  LSTVFile << '\n';
18705  }
18706  LSTVFile.close();
18707  Utilities->CallLogPop();
18708  return(true);
18709 //end of debugging section
18710 */
18711  //declare pointers for use in printouts
18712  TLocServiceTimesVector::iterator Ptr1, Ptr2;
18713 
18714  //set up the output file
18715  AnsiString TTFileName3 = TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss");
18716  TTFileName3 = CurDir + "\\Formatted timetables\\Conflict Analysis " + TTFileName3 + "; " + RailwayTitle + "; " + TimetableTitle + ".csv";
18717 
18718  std::ofstream TTFile3(TTFileName3.c_str());
18719 
18720  if(TTFile3 == 0)
18721  {
18722  ShowMessage("Conflict Analysis file failed to open - can't be created");
18723  Utilities->CallLogPop(2210);
18724  return(false);
18725  }
18726  if(LocServiceTimesVector.empty())
18727  {
18728  ShowMessage("No timetabled services found");
18729  TTFile3.close();
18730  DeleteFile(TTFileName3);
18731  Utilities->CallLogPop(2211);
18732  return(false);
18733  }
18734  TTFile3 << "Timetable analysis for timetable: '" + TimetableTitle + "' in conjunction with railway: '" + RailwayTitle + "'\n";
18735  TTFile3 << "See user manual or on-screen help section 5.12 for detailed information.\n\n\n";
18736  SequenceLog += "6\n";
18737 
18738 /*
18739 //print out TrainDataVectorCopy & TrainDataVector for debugging purposes, TrainDataVectorCopy first
18740 
18741 // Double crosslink (shuttle) table:
18742 //Command Format OtherHead NonRepeating- LinkTrain- NonRepeating- Decsription
18743 // Code ShuttleLink- EntryPtr ShuttleLink-
18744 // HeadCode EntryPtr
18745 
18746 //Snt-sh SNTShuttle Y (rtn shuttle) N Y (rtn sh) N Simple shuttle - no feeder service
18747 //Frh-sh TimeCmdHeadCode Y (outwd shuttle) N Y (outwd sh) N Simple shuttle - no finishing service
18748 //F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
18749 //Sns-sh SNSShuttle Y (rtn shuttle) Y (feeder) Y (rtn) Y (fdr) Shuttle link from feeder service
18750 //Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
18751 //Fns-sh FSHNewService Y (outwd shuttle) Y (finishing) Y (outwd sh) Y (finish) Shuttle link to finishing service
18752 //
18753 //Note: Any shuttle start can have any finish - feeder and finish, neither, feeder but no finish & vice versa.
18754 
18755 //NOTE: from above for F-nshs & Sns-fsh in the TrainDataVectorCopy the OtherLinkedHeadCode will be correct as it is derived from LinkTrainEntryPtr which is correct
18756 //but for the original TrainDataVector the OtherLinkedHeadCode will be incorrect, hence have to use the NonRepeatingShuttleLinkHeadCode as that is the correct one
18757 //these were errors when first coded but work ok, just keep in mind when making any changes
18758 
18759 std::ofstream TDVCFile((CurDir + "\\Formatted timetables\\Conflict Analysis " + TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss") + "; " + RailwayTitle + "; " + TimetableTitle + " TrainDataVectorCopy.txt").c_str());
18760 AnsiString OHC = "", NRHC = "";
18761 AnsiString OLk = "", NRLk = "";
18762 TDVCFile << "Note that for the TrainDataVectorCopy OH and OLk should always be the same, as OH is derived from OHLk; and similarly for NR and NRLk\n\n";
18763 for(TTrainDataVector::iterator TDVCIt = TrainDataVectorCopy.begin(); TDVCIt != TrainDataVectorCopy.end(); TDVCIt++)
18764 {
18765  TDVCFile << TDVCIt->ServiceReference + '\n';
18766  TDVCFile << TDVCIt->Description + '\n';
18767  for(unsigned int x = 0; x < TDVCIt->ActionVector.size(); x++)
18768  {
18769  TActionVectorEntry AVE = TDVCIt->ActionVector.at(x);
18770  if(AVE.OtherHeadCode == "")
18771  {
18772  OHC = "OH 0";
18773  }
18774  else
18775  {
18776  OHC = "OH " + AVE.OtherHeadCode;
18777  }
18778  if(AVE.NonRepeatingShuttleLinkHeadCode == "")
18779  {
18780  NRHC = "NR 0";
18781  }
18782  else
18783  {
18784  NRHC = "NR " + AVE.NonRepeatingShuttleLinkHeadCode;
18785  }
18786  if(TDVCIt->ActionVector.at(x).LinkedTrainEntryPtr == 0)
18787  {
18788  OLk = "OLk 0";
18789  }
18790  else
18791  {
18792  OLk = "OLk " + TDVCIt->ActionVector.at(x).LinkedTrainEntryPtr->ServiceReference;
18793  }
18794  if(TDVCIt->ActionVector.at(x).NonRepeatingShuttleLinkEntryPtr == 0)
18795  {
18796  NRLk = "NRLk 0";
18797  }
18798  else
18799  {
18800  NRLk = "NRLk " + TDVCIt->ActionVector.at(x).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
18801  }
18802 
18803  if(AVE.FormatType == TimeCmd) //cdt only
18804  {
18805  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << '\n';
18806  }
18807  if((AVE.FormatType == TimeCmdHeadCode) || (AVE.FormatType == FNSNonRepeatToShuttle))
18808  {
18809  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
18810  }
18811  else if((AVE.FormatType == FSHNewService) || (AVE.FormatType == SNSShuttle)) //these should have 2 linked services
18812  {
18813  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
18814  }
18815  else if(AVE.FormatType == SNSNonRepeatFromShuttle)
18816  {
18817  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
18818  }
18819  else if(AVE.FormatType == StartNew)
18820  {
18821  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << " Snt RearStartID " << Track->TrackElementAt(-1, AVE.RearStartOrRepeatMins).ElementID
18822  << " FrontStartID " << Track->TrackElementAt(-2, AVE.FrontStartOrRepeatDigits).ElementID << '\n';
18823  }
18824  else if(AVE.FormatType == SNTShuttle)
18825  {
18826  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << " Snt-sh RearStartID " << Track->TrackElementAt(-3, AVE.RearStartOrRepeatMins).ElementID
18827  << " FrontStartID " << Track->TrackElementAt(-4, AVE.FrontStartOrRepeatDigits).ElementID << '\n';
18828  }
18829  else if((AVE.FormatType == TimeLoc) && (AVE.ArrivalTime != TDateTime(-1)))
18830  {
18831  TDVCFile << Utilities->Format96HHMM(AVE.ArrivalTime) << " Arr " << AVE.LocationName << '\n';
18832  }
18833  else if((AVE.FormatType == TimeLoc) && (AVE.DepartureTime != TDateTime(-1)))
18834  {
18835  TDVCFile << Utilities->Format96HHMM(AVE.DepartureTime) << " Dep " << AVE.LocationName << '\n';
18836  }
18837  else if(AVE.FormatType == TimeTimeLoc)
18838  {
18839  TDVCFile << Utilities->Format96HHMM(AVE.ArrivalTime) << ' ' << Utilities->Format96HHMM(AVE.DepartureTime) << ' ' << AVE.LocationName << '\n';
18840  }
18841  else if(AVE.FormatType == PassTime)
18842  {
18843  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << "Pass" << ' ' << AVE.LocationName << '\n';
18844  }
18845  else if(AVE.FormatType == ExitRailway)
18846  {
18847  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << " Fer" << '\n';
18848  }
18849  else if(AVE.FormatType == FinRemHere)
18850  {
18851  TDVCFile << "Frh" << '\n';
18852  }
18853  }
18854  TDVCFile << '\n';
18855 }
18856 TDVCFile.close();
18857 
18858 //print out original TrainDataVector for comparison
18859 std::ofstream TDVFile((CurDir + "\\Formatted timetables\\Conflict Analysis " + TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss") + "; " + RailwayTitle + "; " + TimetableTitle + " TrainDataVector.txt").c_str());
18860 TDVFile << "Note that in the TrainDataVector, non-repeating shuttle link services F-nshs and Sns-fsh use the non-repeating headcode (NR) values for the corresponding "
18861  "shuttle headcodes when it should be the other headcode (OH), and the other headcode is unused. The link values are the right way round. Also OH & NR "
18862  "values ARE headcodes and not service references, but OLk and NRLk values are service references.\n\n";
18863 //F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
18864 //Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
18865 for(TTrainDataVector::iterator TDVIt = TrainDataVector.begin(); TDVIt != TrainDataVector.end(); TDVIt++)
18866 {
18867  TDVFile << TDVIt->ServiceReference + '\n';
18868  TDVFile << TDVIt->Description + '\n';
18869  for(unsigned int x = 0; x < TDVIt->ActionVector.size(); x++)
18870  {
18871  TActionVectorEntry AVE = TDVIt->ActionVector.at(x);
18872  if(AVE.OtherHeadCode == "")
18873  {
18874  OHC = "OH 0";
18875  }
18876  else
18877  {
18878  OHC = "OH " + AVE.OtherHeadCode;
18879  }
18880  if(AVE.NonRepeatingShuttleLinkHeadCode == "")
18881  {
18882  NRHC = "NR 0";
18883  }
18884  else
18885  {
18886  NRHC = "NR " + AVE.NonRepeatingShuttleLinkHeadCode;
18887  }
18888  if(TDVIt->ActionVector.at(x).LinkedTrainEntryPtr == 0)
18889  {
18890  OLk = "OLk 0";
18891  }
18892  else
18893  {
18894  OLk = "OLk " + TDVIt->ActionVector.at(x).LinkedTrainEntryPtr->ServiceReference;
18895  }
18896  if(TDVIt->ActionVector.at(x).NonRepeatingShuttleLinkEntryPtr == 0)
18897  {
18898  NRLk = "NRLk 0";
18899  }
18900  else
18901  {
18902  NRLk = "NRLk " + TDVIt->ActionVector.at(x).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
18903  }
18904 
18905  if(AVE.FormatType == TimeCmd) //cdt only
18906  {
18907  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << '\n';
18908  }
18909  if((AVE.FormatType == TimeCmdHeadCode) || (AVE.FormatType == FNSNonRepeatToShuttle))
18910  {
18911  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
18912  }
18913  else if((AVE.FormatType == FSHNewService) || (AVE.FormatType == SNSShuttle)) //these should have 2 linked services
18914  {
18915  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
18916  }
18917  else if(AVE.FormatType == SNSNonRepeatFromShuttle)
18918  {
18919  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
18920  }
18921  else if(AVE.FormatType == StartNew)
18922  {
18923  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << " Snt RearStartID " << Track->TrackElementAt(-5, AVE.RearStartOrRepeatMins).ElementID
18924  << " FrontStartID " << Track->TrackElementAt(-6, AVE.FrontStartOrRepeatDigits).ElementID << '\n';
18925  }
18926  else if(AVE.FormatType == SNTShuttle)
18927  {
18928  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << " Snt-sh RearStartID " << Track->TrackElementAt(-7, AVE.RearStartOrRepeatMins).ElementID
18929  << " FrontStartID " << Track->TrackElementAt(-8, AVE.FrontStartOrRepeatDigits).ElementID << '\n';
18930  }
18931  else if((AVE.FormatType == TimeLoc) && (AVE.ArrivalTime != TDateTime(-1)))
18932  {
18933  TDVFile << Utilities->Format96HHMM(AVE.ArrivalTime) << " Arr " << AVE.LocationName << '\n';
18934  }
18935  else if((AVE.FormatType == TimeLoc) && (AVE.DepartureTime != TDateTime(-1)))
18936  {
18937  TDVFile << Utilities->Format96HHMM(AVE.DepartureTime) << " Dep " << AVE.LocationName << '\n';
18938  }
18939  else if(AVE.FormatType == TimeTimeLoc)
18940  {
18941  TDVFile << Utilities->Format96HHMM(AVE.ArrivalTime) << ' ' << Utilities->Format96HHMM(AVE.DepartureTime) << ' ' << AVE.LocationName << '\n';
18942  }
18943  else if(AVE.FormatType == PassTime)
18944  {
18945  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << "Pass" << ' ' << AVE.LocationName << '\n';
18946  }
18947  else if(AVE.FormatType == ExitRailway)
18948  {
18949  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << " Fer" << '\n';
18950  }
18951  else if(AVE.FormatType == FinRemHere)
18952  {
18953  TDVFile << "Frh" << '\n';
18954  }
18955  }
18956  TDVFile << '\n';
18957 }
18958 TDVFile.close();
18959 //end of debugging
18960 */
18961  //arrivals
18962  if(ArrChecked)
18963  {
18964  //sort in ArrTime order for each location
18965  Ptr1 = LocServiceTimesVector.begin();
18966  Ptr2 = Ptr1 + 1;
18967  while(Ptr2 != LocServiceTimesVector.end())
18968  {
18969  while(Ptr2->Location == Ptr1->Location) //ends with Ptr2 one past same Location value as Ptr1
18970  {
18971  Ptr2++;
18972  if(Ptr2 == LocServiceTimesVector.end())
18973  {
18974  break;
18975  }
18976  }
18977  std::sort(Ptr1, Ptr2, &LocServiceTimesArrTimeSort);
18978  Ptr1 = Ptr2; //first entry with next name
18979  if(Ptr2 != LocServiceTimesVector.end())
18980  {
18981  Ptr2++;
18982  }
18983  }
18984 
18985  //routine for arrivals - number of trains arriving within the specified range with services listed at the end
18986 
18987  TTFile3 << "Arrival & pass analysis: an asterisk means that the number of same approach code arrivals and passes is equal to or greater than the number of platforms.\n";
18988  TTFile3 << "If the total number of arrivals and passes at the same time exceeds the number of platforms the 'Trains present at location analysis' will show an asterisk.\n\n";
18989  MinuteString = " minutes";
18990  AnsiString ServiceAndRepeatNumTotal = "", ServiceAndRepeatNumTotalOutput = "";
18991  if(ArrRange == 1)
18992  {
18993  MinuteString = " minute";
18994  }
18995  TTFile3 << "Location,Number of,Number of,Services arriving within " << AnsiString(ArrRange) << MinuteString << " with their arrival times and approach codes\n";
18996  TTFile3 << ",Platforms,Trains\n\n";
18997 
18998  Ptr1 = LocServiceTimesVector.begin();
18999  Ptr2 = Ptr1 + 1;
19000  while(Ptr2 != LocServiceTimesVector.end())
19001  {
19002  PreviousService = "";
19003  NumTrainsAtLoc = 0;
19004  ServiceAndRepeatNumTotal = "";
19005  NumPlats = 0;
19006  NumPlatsAtThisLocCalculated = false;
19007  BasicTime = "";
19008  while((Ptr2->Location != Ptr1->Location) || ((Ptr1->Location == "") && (Ptr2->Location == "")))
19009  {
19010  PreviousService = "";
19011  NumTrainsAtLoc = 0;
19012  ServiceAndRepeatNumTotal = "";
19013  NumPlats = 0;
19014  NumPlatsAtThisLocCalculated = false;
19015  BasicTime = "";
19016  Ptr1++;
19017  Ptr2++;
19018  if(Ptr2 == LocServiceTimesVector.end())
19019  {
19020  break;
19021  }
19022  }
19023  if(Ptr2 == LocServiceTimesVector.end())
19024  {
19025  break;
19026  }
19027  while(Ptr2->Location == Ptr1->Location)
19028  {
19029  PreviousService = "";
19030  NumTrainsAtLoc = 0;
19031  ServiceAndRepeatNumTotal = "";
19032  BasicTime = Ptr1->ArrTime; //used to compare later times - later pointer contents have same or later times as sorted in time order
19033  if((Ptr1->Location == "") && (Ptr2->Location == ""))
19034  {
19035  break;
19036  }
19037  while(!WithinTimeRange(0, BasicTime, Ptr2->ArrTime, ArrRange) || ((Ptr1->ArrTime == "") && (Ptr2->ArrTime == "")))
19038  {
19039  BasicTime = Ptr2->ArrTime; //used to compare later times or last can exceed first
19040  Ptr1++;
19041  Ptr2++;
19042  if(Ptr2 == LocServiceTimesVector.end())
19043  {
19044  break;
19045  }
19046  if(Ptr2->Location != Ptr1->Location)
19047  {
19048  break;
19049  }
19050  }
19051  if(Ptr2 == LocServiceTimesVector.end())
19052  {
19053  break;
19054  }
19055  if(Ptr2->Location != Ptr1->Location)
19056  {
19057  break;
19058  }
19059  while(WithinTimeRange(1, BasicTime, Ptr2->ArrTime, ArrRange))
19060  {
19061  if((Ptr1->ArrTime == "") && (Ptr2->ArrTime == ""))
19062  {
19063  break;
19064  }
19065  if(!NumPlatsAtThisLocCalculated) //num plats at relevant location, reset when locations change
19066  {
19067  NumPlats = Track->NumberOfPlatforms(0, Ptr1->Location);
19068  NumPlatsAtThisLocCalculated = true;
19069  }
19070  if(Ptr1->ServiceAndRepeatNum != PreviousService) //don't print it twice if same as last - as will be if >1 service at same loc at same time
19071  {
19072  if(ServiceAndRepeatNumTotal == "")
19073  {
19074  ServiceAndRepeatNumTotal = Ptr1->ServiceAndRepeatNum + "," + Ptr1->ArrTime;
19075  NumTrainsAtLoc = 1;
19076  }
19077  else
19078  {
19079  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr1->ServiceAndRepeatNum + "," + Ptr1->ArrTime;
19080  }
19081  }
19082  PreviousService = Ptr2->ServiceAndRepeatNum; //last service at relevant time, reset when times differ
19083  if(ServiceAndRepeatNumTotal == "")
19084  {
19085  ServiceAndRepeatNumTotal = Ptr2->ServiceAndRepeatNum + "," + Ptr2->ArrTime;
19086  NumTrainsAtLoc = 1;
19087  }
19088  else
19089  {
19090  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr2->ServiceAndRepeatNum + "," + Ptr2->ArrTime;
19091  }
19092  Ptr1 = Ptr2;
19093  Ptr2++;
19094  if((Ptr2 == LocServiceTimesVector.end()) || (Ptr2->Location != Ptr1->Location) || (!WithinTimeRange(2, BasicTime, Ptr2->ArrTime, ArrRange)))
19095  {
19096  int MaxNumberOfSameDirections = 0;
19097  ServiceAndRepeatNumTotalOutput = ConsolidateSARNTArrDep(1, ServiceAndRepeatNumTotal, NumTrainsAtLoc, Ptr1->Location, true, AnalysisError, MaxNumberOfSameDirections); //sort into alphabetical order and remove duplicates
19098  if(AnalysisError) //has to be Ptr1->Location as Ptr2 loc may have changed
19099  {
19100 // ShowMessage("Error in arrival analysis - file will be incomplete and/or corrupt. Please send railway and timetable files to railwayfeedback@gmail.com for investigation - thanks. Details: " + ServiceAndRepeatNumTotalOutput);
19101  TTFile3.close();
19102  throw Exception(ServiceAndRepeatNumTotalOutput.c_str());
19103 // Utilities->CallLogPop(2224);
19104 // return false;
19105  }
19106  AnsiString Asterisk = "";
19107  if(MaxNumberOfSameDirections >= NumPlats)
19108  {
19109  Asterisk = "* ";
19110  }
19111  //print out a single line for number of trains at loc with all service refs
19112  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << ServiceAndRepeatNumTotalOutput << '\n'; //no description as >1 service
19113  ArrivalsPrinted = true;
19114  ServiceAndRepeatNumTotal = "";
19115  }
19116  if(Ptr2 == LocServiceTimesVector.end())
19117  {
19118  break;
19119  }
19120  if(Ptr2->Location != Ptr1->Location)
19121  {
19122  break;
19123  }
19124  }
19125  if(Ptr2 == LocServiceTimesVector.end())
19126  {
19127  break;
19128  }
19129  }
19130  }
19131  if(!ArrivalsPrinted)
19132  {
19133  TTFile3 << "Nothing to report for arrivals";
19134  }
19135  TTFile3 << "\n\n";
19136  }
19137  //end of routine for arrivals
19138  SequenceLog += "7\n";
19139  //departures
19140  if(DepChecked)
19141  {
19142  //sort in DepTime order for each location
19143  Ptr1 = LocServiceTimesVector.begin();
19144  Ptr2 = Ptr1 + 1;
19145  while(Ptr2 != LocServiceTimesVector.end())
19146  {
19147  while(Ptr2->Location == Ptr1->Location) //ends with Ptr2 one past same Location value as Ptr1
19148  {
19149  Ptr2++;
19150  if(Ptr2 == LocServiceTimesVector.end())
19151  {
19152  break;
19153  }
19154  }
19155  std::sort(Ptr1, Ptr2, &LocServiceTimesDepTimeSort);
19156  Ptr1 = Ptr2; //first entry with next name
19157  if(Ptr2 != LocServiceTimesVector.end())
19158  {
19159  Ptr2++;
19160  }
19161  }
19162 
19163  //routine for departures - number of trains departing within the specified range with services listed at the end
19164  TTFile3 << "Departure & pass analysis: an asterisk means that the number of same exit code departures and passes is equal to or greater than the number of platforms.\n";
19165  TTFile3 << "If the total number of departures and passes at the same time exceeds the number of platforms the 'Trains present at location analysis' will show an asterisk.\n\n";
19166  MinuteString = " minutes";
19167  AnsiString ServiceAndRepeatNumTotal = "", ServiceAndRepeatNumTotalOutput = "";
19168  if(DepRange == 1)
19169  {
19170  MinuteString = " minute";
19171  }
19172  TTFile3 << "Location,Number of,Number of,Services departing within " << AnsiString(DepRange) << MinuteString << " with their departure times and exit codes\n";
19173  TTFile3 << ",Platforms,Trains\n\n";
19174 
19175  Ptr1 = LocServiceTimesVector.begin();
19176  Ptr2 = Ptr1 + 1;
19177  while(Ptr2 != LocServiceTimesVector.end())
19178  {
19179  PreviousService = "";
19180  NumTrainsAtLoc = 0;
19181  ServiceAndRepeatNumTotal = "";
19182  NumPlats = 0;
19183  NumPlatsAtThisLocCalculated = false;
19184  BasicTime = "";
19185  while((Ptr2->Location != Ptr1->Location) || ((Ptr1->Location == "") && (Ptr2->Location == "")))
19186  {
19187  PreviousService = "";
19188  NumTrainsAtLoc = 0;
19189  ServiceAndRepeatNumTotal = "";
19190  NumPlats = 0;
19191  NumPlatsAtThisLocCalculated = false;
19192  BasicTime = "";
19193  Ptr1++;
19194  Ptr2++;
19195  if(Ptr2 == LocServiceTimesVector.end())
19196  {
19197  break;
19198  }
19199  }
19200  if(Ptr2 == LocServiceTimesVector.end())
19201  {
19202  break;
19203  }
19204  while(Ptr2->Location == Ptr1->Location)
19205  {
19206  PreviousService = "";
19207  NumTrainsAtLoc = 0;
19208  ServiceAndRepeatNumTotal = "";
19209  BasicTime = Ptr1->DepTime; //used to compare later times - later pointer contents have same or later times as sorted in time order
19210  if((Ptr1->Location == "") && (Ptr2->Location == ""))
19211  {
19212  break;
19213  }
19214  while(!WithinTimeRange(3, BasicTime, Ptr2->DepTime, DepRange) || ((Ptr1->DepTime == "") && (Ptr2->DepTime == "")))
19215  {
19216  BasicTime = Ptr2->DepTime; //used to compare later times or last can exceed first
19217  Ptr1++;
19218  Ptr2++;
19219  if(Ptr2 == LocServiceTimesVector.end())
19220  {
19221  break;
19222  }
19223  if(Ptr2->Location != Ptr1->Location)
19224  {
19225  break;
19226  }
19227  }
19228  if(Ptr2 == LocServiceTimesVector.end())
19229  {
19230  break;
19231  }
19232  if(Ptr2->Location != Ptr1->Location)
19233  {
19234  break;
19235  }
19236  while(WithinTimeRange(4, BasicTime, Ptr2->DepTime, DepRange))
19237  {
19238  if((Ptr1->DepTime == "") && (Ptr2->DepTime == ""))
19239  {
19240  break;
19241  }
19242  if(!NumPlatsAtThisLocCalculated) //num plats at relevant location, reset when locations change
19243  {
19244  NumPlats = Track->NumberOfPlatforms(1, Ptr1->Location);
19245  NumPlatsAtThisLocCalculated = true;
19246  }
19247  if(Ptr1->ServiceAndRepeatNum != PreviousService) //don't print it twice if same as last - as will be if >1 service at same loc at same time
19248  {
19249  if(ServiceAndRepeatNumTotal == "")
19250  {
19251  ServiceAndRepeatNumTotal = Ptr1->ServiceAndRepeatNum + "," + Ptr1->DepTime;
19252  NumTrainsAtLoc = 1;
19253  }
19254  else
19255  {
19256  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr1->ServiceAndRepeatNum + "," + Ptr1->DepTime;
19257  }
19258  }
19259  PreviousService = Ptr2->ServiceAndRepeatNum; //last service at relevant time, reset when times differ
19260  if(ServiceAndRepeatNumTotal == "")
19261  {
19262  ServiceAndRepeatNumTotal = Ptr2->ServiceAndRepeatNum + "," + Ptr2->DepTime;
19263  NumTrainsAtLoc = 1;
19264  }
19265  else
19266  {
19267  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr2->ServiceAndRepeatNum + "," + Ptr2->DepTime;
19268  }
19269  Ptr1 = Ptr2;
19270  Ptr2++;
19271  if((Ptr2 == LocServiceTimesVector.end()) || (Ptr2->Location != Ptr1->Location) || (!WithinTimeRange(5, BasicTime, Ptr2->DepTime, DepRange)))
19272  {
19273  int MaxNumberOfSameDirections = 0;
19274  ServiceAndRepeatNumTotalOutput = ConsolidateSARNTArrDep(3, ServiceAndRepeatNumTotal, NumTrainsAtLoc, Ptr1->Location, false, AnalysisError, MaxNumberOfSameDirections); //sort into alphabetical order and remove duplicates
19275  if(AnalysisError) //has to be Ptr1->Location as Ptr2 loc may have changed
19276  {
19277 // ShowMessage("Error in departure analysis - file will be incomplete and/or corrupt. Please send railway and timetable files to railwayfeedback@gmail.com for investigation - thanks. Details: " + ServiceAndRepeatNumTotalOutput);
19278  TTFile3.close();
19279  throw Exception(ServiceAndRepeatNumTotalOutput.c_str());
19280 // Utilities->CallLogPop(2225);
19281 // return false;
19282  }
19283  AnsiString Asterisk = "";
19284  if(MaxNumberOfSameDirections >= NumPlats)
19285  {
19286  Asterisk = "* ";
19287  }
19288  //print out a single line for number of trains at loc with all service refs
19289  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << ServiceAndRepeatNumTotalOutput << '\n'; //no description as >1 service
19290  DeparturesPrinted = true;
19291  ServiceAndRepeatNumTotal = "";
19292  }
19293  if(Ptr2 == LocServiceTimesVector.end())
19294  {
19295  break;
19296  }
19297  if(Ptr2->Location != Ptr1->Location)
19298  {
19299  break;
19300  }
19301  }
19302  if(Ptr2 == LocServiceTimesVector.end())
19303  {
19304  break;
19305  }
19306  }
19307  }
19308  if(!DeparturesPrinted)
19309  {
19310  TTFile3 << "Nothing to report for departures";
19311  }
19312  TTFile3 << "\n\n";
19313  }
19314  //end of routine for departures
19315  SequenceLog += "8\n";
19316 
19317  //list trains at locations at same time
19318 
19319  if(AtLocChecked)
19320  {
19321  //sort in AtLocTime order for each location
19322  Ptr1 = LocServiceTimesVector.begin();
19323  Ptr2 = Ptr1 + 1;
19324  while(Ptr2 != LocServiceTimesVector.end())
19325  {
19326  while(Ptr2->Location == Ptr1->Location) //ends with Ptr2 one past same Location value as Ptr1
19327  {
19328  Ptr2++;
19329  if(Ptr2 == LocServiceTimesVector.end())
19330  {
19331  break;
19332  }
19333  }
19334  std::sort(Ptr1, Ptr2, &LocServiceTimesAtLocTimeSort);
19335  Ptr1 = Ptr2; //first entry with next name
19336  if(Ptr2 != LocServiceTimesVector.end())
19337  {
19338  Ptr2++;
19339  }
19340  }
19341 
19342  //print out simultaneous AtLocs (don't need range of times for AtLocs)
19343  TTFile3 << "Trains present at location analysis: an asterisk means that the number of trains at the location is greater than the number of platforms.\n\n";
19344  TTFile3 << "Location,Number of,Number of,Time,Services at the location at that time\n";
19345  TTFile3 << ",Platforms,Trains,\n\n";
19346  AnsiString ServiceAndRepeatNumTotal = "", ServiceAndRepeatNumTotalOutput = "";
19347  Ptr1 = LocServiceTimesVector.begin();
19348  Ptr2 = Ptr1 + 1;
19349  while(Ptr2 != LocServiceTimesVector.end())
19350  {
19351  PreviousService = "";
19352  ServiceAndRepeatNumTotal = "";
19353  NumTrainsAtLoc = 0;
19354  NumPlats = 0;
19355  NumPlatsAtThisLocCalculated = false;
19356  FrhCount = 0;
19357 
19358  while((Ptr2->Location != Ptr1->Location) || ((Ptr1->Location == "") && (Ptr2->Location == "")))
19359  {
19360  PreviousService = "";
19361  ServiceAndRepeatNumTotal = "";
19362  NumTrainsAtLoc = 0;
19363  NumPlats = 0;
19364  NumPlatsAtThisLocCalculated = false;
19365  FrhCount = 0;
19366  Ptr1++;
19367  Ptr2++;
19368  if(Ptr2 == LocServiceTimesVector.end())
19369  {
19370  break;
19371  }
19372  }
19373  if(Ptr2 == LocServiceTimesVector.end())
19374  {
19375  break;
19376  }
19377  while(Ptr2->Location == Ptr1->Location)
19378  {
19379  if(Ptr1->FrhMarker == "Frh") //this test is made here and each time Ptr1 increases with Ptr1 & 2 at same loc so as to catch them all
19380  {
19381  FrhCount++;
19382  Ptr1->FrhMarker = "FrhCounted"; //to avoid double counting
19383  }
19384  PreviousService = "";
19385  NumTrainsAtLoc = 0;
19386  ServiceAndRepeatNumTotal = "";
19387  if((Ptr1->Location == "") && (Ptr2->Location == ""))
19388  {
19389  break;
19390  }
19391  while((Ptr2->AtLocTime != Ptr1->AtLocTime) || ((Ptr1->AtLocTime == "") && (Ptr2->AtLocTime == "")))
19392  {
19393  Ptr1++;
19394  if(Ptr1->FrhMarker == "Frh")
19395  {
19396  FrhCount++;
19397  Ptr1->FrhMarker = "FrhCounted"; //to avoid double counting
19398  }
19399  Ptr2++;
19400  if(Ptr2 == LocServiceTimesVector.end())
19401  {
19402  break;
19403  }
19404  if(Ptr2->Location != Ptr1->Location)
19405  {
19406  break;
19407  }
19408  }
19409  if(Ptr2 == LocServiceTimesVector.end())
19410  {
19411  break;
19412  }
19413  if(Ptr2->Location != Ptr1->Location)
19414  {
19415  break;
19416  }
19417  while(Ptr2->AtLocTime == Ptr1->AtLocTime)
19418  {
19419  if((Ptr1->AtLocTime == "") && (Ptr2->AtLocTime == ""))
19420  {
19421  break;
19422  }
19423  if(!NumPlatsAtThisLocCalculated) //num plats at relevant location, reset when locations change
19424  {
19425  NumPlats = Track->NumberOfPlatforms(2, Ptr1->Location);
19426  NumPlatsAtThisLocCalculated = true;
19427  }
19428  if(Ptr1->ServiceAndRepeatNum != PreviousService) //don't print it twice if same as last - as will be if >1 service at same loc at same time
19429  {
19430  if(ServiceAndRepeatNumTotal == "")
19431  {
19432  ServiceAndRepeatNumTotal = Ptr1->ServiceAndRepeatNum;
19433  NumTrainsAtLoc = 1;
19434  }
19435  else
19436  {
19437  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr1->ServiceAndRepeatNum;
19438  }
19439  }
19440  PreviousService = Ptr2->ServiceAndRepeatNum; //last service at relevant time, reset when times differ, has to be Ptr2 to compare Ptr1 at next round when incremented
19441  if(ServiceAndRepeatNumTotal == "")
19442  {
19443  ServiceAndRepeatNumTotal = Ptr2->ServiceAndRepeatNum;
19444  NumTrainsAtLoc = 1;
19445  }
19446  else
19447  {
19448  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr2->ServiceAndRepeatNum;
19449  }
19450  Ptr1 = Ptr2;
19451  if(Ptr1->FrhMarker == "Frh")
19452  {
19453  FrhCount++;
19454  Ptr1->FrhMarker = "FrhCounted"; //to avoid double counting
19455  }
19456  Ptr2++;
19457  if((Ptr2 == LocServiceTimesVector.end()) || (Ptr2->Location != Ptr1->Location) || (Ptr2->AtLocTime != Ptr1->AtLocTime))
19458  {
19459 //old text //only print out if no remainers (1st condition), change in remainers (2nd condition) or change in ServiceAndRepeatNumTotalOutput, and >1 train (later condition)
19460 //new text //don't print out if all remainers or if only 1 train at loc
19461  ServiceAndRepeatNumTotalOutput = ConsolidateSARNTAtLoc(1, ServiceAndRepeatNumTotal, NumTrainsAtLoc); //sort into alphabetical order, remove duplicates, and calculate new value for NumTrainsAtLoc
19462 //old condits if((FrhCount == 0) || (FrhCount != LastFrhCount) || (PreviousServiceAndRepeatNumTotalOutput != ServiceAndRepeatNumTotalOutput))//don't print if same output
19463 /*new condits*/ if((NumTrainsAtLoc > 1) && ((FrhCount < NumTrainsAtLoc) || (FrhCount != LastFrhCount)))
19464  {
19465  AnsiString Asterisk = "";
19466  if(NumTrainsAtLoc > NumPlats)
19467  {
19468  Asterisk = "* ";
19469  }
19470  //print out a single line for number of trains at loc with all service refs
19471  if(FrhCount == 0)
19472  {
19473  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << Ptr1->AtLocTime << "," << ServiceAndRepeatNumTotalOutput << '\n';
19474  }
19475  else if(FrhCount == 1)
19476  {
19477  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << Ptr1->AtLocTime << " (1 remains here)," << ServiceAndRepeatNumTotalOutput << '\n';
19478  }
19479  else
19480  {
19481  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << Ptr1->AtLocTime << " (" << FrhCount << " remain here)," << ServiceAndRepeatNumTotalOutput << '\n';
19482  }
19483  LastFrhCount = FrhCount;
19484  PreviousServiceAndRepeatNumTotalOutput = ServiceAndRepeatNumTotalOutput;
19485  AtLocsPrinted = true;
19486  ServiceAndRepeatNumTotal = "";
19487  }
19488  }
19489  if(Ptr2 == LocServiceTimesVector.end())
19490  {
19491  break;
19492  }
19493  if(Ptr2->Location != Ptr1->Location)
19494  {
19495  break;
19496  }
19497  }
19498  if(Ptr2 == LocServiceTimesVector.end())
19499  {
19500  break;
19501  }
19502  }
19503  }
19504  if(!AtLocsPrinted)
19505  {
19506  TTFile3 << "Nothing to report for trains at locations";
19507  }
19508  TTFile3 << "\n\n";
19509  //end of simultaneous AtLocs
19510  }
19511  SequenceLog += "9\n";
19512 
19513 /*
19514 //start of debugging section
19515  //print out the full vector here for testing purposes
19516  TTFile3 << "Full LocServiceTimesVector\n\n";
19517  TTFile3 << "Location,AtLocTime,ArrTime,DepTime,ServiceAndRepeatNum,Description\n\n";
19518 
19519  for(TLocServiceTimesVector::iterator Ptr = LocServiceTimesVector.begin(); Ptr != LocServiceTimesVector.end(); Ptr++)
19520  {
19521  TTFile3 << Ptr->Location << "," << Ptr->AtLocTime << "," << Ptr->ArrTime << "," << Ptr->DepTime << "," << Ptr->ServiceAndRepeatNum << "," << Ptr->FrhMarker << '\n';
19522  }
19523 
19524  TTFile3 << "\n\n\n";
19525 //end of debugging
19526 */
19527 
19528 /*cdt analysis - added at v2.10.0
19529 2 pass system: 1st extract as a single service all Snt (or Snt-sh) starts, with Fns/Sns links combined (and F-nshs/Sns-sh) (though add a new
19530 changeover code [chr XXXX - 'change ref + new reference] until come to Fjo, Frh, Frh-sh, Fer (ignore exit loc as can't stop there), ignore jbos &
19531 repeats, but with fsp & rsp store all the foregoing service entries along with the split reference & add that to the relevant Sfs entry as a new
19532 service. For shuttles with feeder start with feeder & progress into shuttle, ending when finish & remain here or progressing into the finishing
19533 service.
19534 
19535 Use The TrainDataVectorCopy as that has all unique service refs.
19536 
19537 2nd run the cdt checker similar to that in SecondPassActions, but where a same name found either side of a changeover code quote both refs. Add a
19538 similar unexpected cdt check where if have different locs either side of a cdt then may be inappropriate.
19539 
19540 First create a new TrainDataVector from earlier copy as above with single services
19541 */
19542  if(DirChecked)
19543  {
19544  //direction analysis added at v2.10.0
19545  TTrainDataEntry SingleServiceEntry, PartServiceEntry, NewPartServiceEntry, TempEntry;
19546  TTrainDataVector SingleServiceVector, PartServiceVector;
19547 
19548  //find new train services (Snt or Snt-sh) & remember that entries can be in any order
19549  //NB: ALWAYS use OtherHeadCode (which is now a service reference) for any follow-on service
19550  TTFile3 << "Train direction analysis - consisting of train facing directions on creation and possible missing or questionable changes of direction:\n\n";
19551  for(unsigned int x = 0; x < TrainDataVectorCopy.size(); x++)
19552  {
19554  if(TDE.ActionVector.at(TDE.ActionVector.size() - 1).FormatType == Repeat)
19555  {
19556  TDE.ActionVector.erase(TDE.ActionVector.end() - 1); //strip repeat entry if present
19557  }
19558  const TActionVector &AV = TDE.ActionVector;
19559  if((AV.at(0).Command == "Snt") || (AV.at(0).Command == "Snt-sh"))
19560  {
19561  SingleServiceEntry = TDE;
19562  TActionVector &SSAV = SingleServiceEntry.ActionVector;
19563  for(unsigned int y = 0; y < SSAV.size(); y++)
19564  {
19565  if((SSAV.at(y).Command == "Fjo") || (SSAV.at(y).Command == "Frh") || (SSAV.at(y).Command == "Fer") || (SSAV.at(y).Command == "Frh-sh"))
19566  {
19567  SingleServiceVector.push_back(SingleServiceEntry); //push the complete entry
19568  break; //finished with this one
19569  }
19570  else if((SSAV.at(y).Command == "fsp") || (SSAV.at(y).Command == "rsp"))
19571  {
19572  PartServiceEntry = TDE; //start with complete entry
19573  PartServiceEntry.ActionVector.clear(); //clear AV
19574  for(unsigned int z = 0; z <= y; z++)
19575  {
19576  PartServiceEntry.ActionVector.push_back(TDE.ActionVector.at(z)); //add back all AVs up to & inc fsp/rsp
19577  if(z == y)
19578  {
19579  PartServiceEntry.ActionVector.at(z).Command = "chr-sp"; //change split command to chr
19580  PartServiceEntry.ActionVector.at(z).OtherHeadCode = PartServiceEntry.ActionVector.at(z).LinkedTrainEntryPtr->ServiceReference;
19581  }
19582  }
19583  PartServiceVector.push_back(PartServiceEntry);
19584  if(SSAV.at(y).Command == "fsp")
19585  {
19586  SSAV.at(y).Command = "Front split - original service continues below";
19587  SSAV.at(y).OtherHeadCode = "";
19588  }
19589  if(SSAV.at(y).Command == "rsp")
19590  {
19591  SSAV.at(y).Command = "Rear split - original service continues below";
19592  SSAV.at(y).OtherHeadCode = "";
19593  }
19594  //don't break & continue here because the original train carries on
19595  }
19596  else if(SSAV.at(y).Command == "Fns")
19597  {
19598  SSAV.at(y).Command = "chr-Fns";
19599  SSAV.at(y).OtherHeadCode = SSAV.at(y).LinkedTrainEntryPtr->ServiceReference;
19600  PartServiceVector.push_back(SingleServiceEntry); //not complete yet
19601  break; //from y loop
19602  }
19603  else if(SSAV.at(y).Command == "Fns-sh")
19604  {
19605  SSAV.at(y).Command = "chr-Fns-sh";
19606  SSAV.at(y).OtherHeadCode = SSAV.at(y).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
19607  SSAV.at(y).NonRepeatingShuttleLinkHeadCode = "";
19608  PartServiceVector.push_back(SingleServiceEntry); //not complete yet
19609  break; //from y loop
19610  }
19611  else if(SSAV.at(y).Command == "F-nshs")
19612  {
19613  SSAV.at(y).Command = "chr-F-nshs"; //NonRepeatingShuttleLinkHeadCode is the shuttle headcode
19614  SSAV.at(y).OtherHeadCode = SSAV.at(y).LinkedTrainEntryPtr->ServiceReference;
19615  SSAV.at(y).NonRepeatingShuttleLinkHeadCode = "";
19616  PartServiceVector.push_back(SingleServiceEntry); //not complete yet
19617  break; //from y loop
19618  }
19619  }
19620  }
19621  }
19622  SequenceLog += "10\n";
19623  //now have all complete entries in SingleServiceVector and all part services in PartServiceVector but without any follow-ons
19624 
19625  //Now add Sns & Sns-sh services to PartServiceVector entries
19626  AnsiString NextRef;
19627  while(!PartServiceVector.empty())
19628  {
19629  PartServiceEntry = PartServiceVector.at(0); //deal with front entry and add new entries at the back
19630  for(unsigned int y = 0; y < PartServiceEntry.ActionVector.size(); y++)
19631  {
19632  if(PartServiceEntry.ActionVector.at(y).Command.SubString(1,3) == "chr")
19633  {
19634  NextRef = PartServiceEntry.ActionVector.at(y).OtherHeadCode;
19635  }
19636  }
19637  //find it in TrainDataVectorCopy
19638  bool FinishType = true, FoundFlag = false;
19639  while(FinishType)
19640  {
19641  TempEntry = GetServiceFromVector(0, NextRef, TrainDataVectorCopy, FinishType, FoundFlag); //FinishType is a bool where false = Final (Fjo, Frh, Fer, or
19642  //Frh-sh); true = MoreToCome (Fns, Fns-sh, F-nshs)
19643  if(FoundFlag)
19644  {
19645  for(unsigned int y = 1; y < TempEntry.ActionVector.size(); y++) //starts at 1 as that is the entry after the start entry
19646  {
19647  if((TempEntry.ActionVector.at(y).Command == "") && (TempEntry.ActionVector.at(y).FormatType != Repeat))
19648  {
19649  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y));
19650  }
19651  else if((TempEntry.ActionVector.at(y).Command[1] != 'F') && (TempEntry.ActionVector.at(y).Command != "fsp") && (TempEntry.ActionVector.at(y).Command != "rsp") && (TempEntry.ActionVector.at(y).FormatType != Repeat))
19652  {
19653  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y));
19654  }
19655  else
19656  {
19657  if((TempEntry.ActionVector.at(y).Command == "Fjo") || (TempEntry.ActionVector.at(y).Command == "Frh") || (TempEntry.ActionVector.at(y).Command == "Fer") || (TempEntry.ActionVector.at(y).Command == "Frh-sh"))
19658  {
19659  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y));
19660  SingleServiceVector.push_back(PartServiceVector.at(0)); //push the complete entry
19661  PartServiceVector.erase(PartServiceVector.begin());
19662  break; //from y loop
19663  }
19664  else if((TempEntry.ActionVector.at(y).Command == "fsp") || (TempEntry.ActionVector.at(y).Command == "rsp"))
19665  {
19666  NewPartServiceEntry = PartServiceVector.at(0); //covers everything up to but excluding the split
19667  NewPartServiceEntry.ActionVector.push_back(TempEntry.ActionVector.at(y)); //now includes the split
19668  NewPartServiceEntry.ActionVector.at(NewPartServiceEntry.ActionVector.size() - 1).Command = "chr-sp"; //change split command to chr
19669  NewPartServiceEntry.ActionVector.at(NewPartServiceEntry.ActionVector.size() - 1).OtherHeadCode = NewPartServiceEntry.ActionVector.at(NewPartServiceEntry.ActionVector.size() - 1).LinkedTrainEntryPtr->ServiceReference;
19670  PartServiceVector.push_back(NewPartServiceEntry); //new entry for the split service
19671  if(TempEntry.ActionVector.at(y).Command == "fsp")
19672  {
19673  TempEntry.ActionVector.at(y).Command = "Front split - original service continues below";
19674  TempEntry.ActionVector.at(y).OtherHeadCode = "";
19675  }
19676  if(TempEntry.ActionVector.at(y).Command == "rsp")
19677  {
19678  TempEntry.ActionVector.at(y).Command = "Rear split - original service continues below";
19679  TempEntry.ActionVector.at(y).OtherHeadCode = "";
19680  }
19681  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y));
19682  }
19683  else if(TempEntry.ActionVector.at(y).Command == "Fns")
19684  {
19685  TempEntry.ActionVector.at(y).Command = "chr-Fns";
19686  NextRef = TempEntry.ActionVector.at(y).LinkedTrainEntryPtr->ServiceReference;
19687  TempEntry.ActionVector.at(y).OtherHeadCode = NextRef;
19688  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y)); //not complete yet
19689  break; //from y loop
19690  }
19691  else if(TempEntry.ActionVector.at(y).Command == "Fns-sh")
19692  {
19693  TempEntry.ActionVector.at(y).Command = "chr-Fns-sh";
19694  TempEntry.ActionVector.at(y).OtherHeadCode = TempEntry.ActionVector.at(y).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
19695  TempEntry.ActionVector.at(y).NonRepeatingShuttleLinkHeadCode = "";
19696  NextRef = TempEntry.ActionVector.at(y).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
19697  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y)); //not complete yet
19698  break; //from y loop
19699  }
19700  else if(TempEntry.ActionVector.at(y).Command == "F-nshs")
19701  {
19702  TempEntry.ActionVector.at(y).Command = "chr-F-nshs"; //NonRepeatingShuttleLinkHeadCode is the shuttle headcode
19703  TempEntry.ActionVector.at(y).OtherHeadCode = TempEntry.ActionVector.at(y).LinkedTrainEntryPtr->ServiceReference;
19704  TempEntry.ActionVector.at(y).NonRepeatingShuttleLinkHeadCode = "";
19705  NextRef = TempEntry.ActionVector.at(y).LinkedTrainEntryPtr->ServiceReference;
19706  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y)); //not complete yet
19707  break; //from y loop
19708  }
19709  }
19710  }
19711  }
19712  else
19713  {
19714  SequenceLog += + "11\n";
19715  throw Exception("Unable to find service reference " + NextRef + " Last ref checked = " + TempEntry.ServiceReference);
19716  }
19717  }
19718  }
19719  if(!PartServiceVector.empty())
19720  {
19721  SequenceLog += "12\n";
19722  throw Exception("PartServiceVector should be empty here - size = " + AnsiString(PartServiceVector.size()));
19723  }
19724  SequenceLog += "13\n";
19725  /*
19726  form:-
19727  HeadCode[;Description (plain text, no commas or semicolons)][;StartSpeed(kph); MaxRunningSpeed(kph); Mass(tonnes, prog converts to kg);
19728  MaxBrakeRate(tonnes force, prog converts to m/s/s); & gross power(kW, prog converts to power at rail in w)
19729  then multiple entries, separated by commas, of the form:-
19730 
19731  HH:MM;Snt;RearStartIdent FrontStartIdent }StartNew }
19732  HH:MM;Snt-sh;RearStartIdent FrontStartIdent;Fsh HeadCode }SNTShuttle }
19733  HH:MM;Sns-sh;Fxx-sh HeadCode;F-nshs HeadCode (non-repeating)}SNSShuttle }
19734 
19735  HH:MM;Command;HeadCode (Sfs Sns jbo fsp rsp Fns Fjo Frh-sh) }TimeCmdHeadCode } Train action entries
19736  HH:MM;F-nshs;NonRepeatingShuttleLinkHeadCode }FNSNonRepeatToShuttle }
19737  HH:MM;Sns-fsh;NonRepeatingShuttleLinkHeadCode }SNSNonRepeatFromShuttle }
19738 
19739  HH:MM;Command (cdt) }TimeCmd }
19740  HH:MM;Command;new description (dsc) }TimeCmdDescription }
19741  HH:MM;Location (arr & dep) }TimeLoc }
19742  HH:MM;HH:MM;Location }TimeTimeLoc }
19743  HH:MM;pas;Location }PassTime }
19744  HH:MM;Fns-sh;Snx-sh HeadCode;Sns-fsh HeadCode (non-rep) }FSHNewService }
19745  HH:MM;Fer;set of allowable IDs }ExitRailway }
19746  Command (Frh only) }FinRemHere }
19747 
19748  R;mm;dd;nn. Repeat Repeat entry
19749 
19750  Formats:
19751 
19752  Command only: Frh
19753  Time;Command: cdt
19754  Time;Command;new description: dsc
19755  Time;Command;Headcode: Sfs Sns jbo fsp rsp Fns Fjo Frh-sh F-nshs Sns-fsh
19756  Time;Command;2 Element IDs: Snt
19757  Time;Comand;n Element IDs: Fer
19758  Time;Command;rep Headcode;nonrep Headcode: Sns-sh Fns-sh
19759  Time;Command;2 Element IDs;Headcode Snt-sh
19760  Time;Command;Location pas
19761  Time;Location Arr Dep
19762  Time;Time;Location Arr & dep together
19763  */
19764 
19765 /*
19766 Perform the starting direction check (Snt & Snt-sh entries). Starts with the train's front element,
19767 checking forwards until it comes to a continuation (no report), a location name that is not null and
19768 different to the train's front element name (whether null or not) (no report), a leading point
19769 (no report) or buffers (report).
19770 */
19771  bool BufferFacingUnReportedFlag = true;
19772  bool TrainFacingBuffersReported = false; //new flag added at v2.19.0 for 'no facing buffers' message instead of BufferFacingUnReportedFlag
19773  for(unsigned int x = 0; x < SingleServiceVector.size(); x++)
19774  {
19775  TTrackElement ThisElement, NextElement;
19776  TTrainDataEntry TDE = SingleServiceVector.at(x);
19777  if(TDE.ActionVector.at(TDE.ActionVector.size() - 1).FormatType == Repeat)
19778  {
19779  SequenceLog += "13a\n";
19780  throw Exception("Repeat entry present in SingleServiceVector at position " + AnsiString(x));
19781  }
19782  const TActionVector &AV = TDE.ActionVector;
19783  if((AV.at(0).Command == "Snt") || (AV.at(0).Command == "Snt-sh"))
19784  {
19785  bool BufferFlag = false;
19786  int FrontTVPos = AV.at(0).FrontStartOrRepeatDigits;
19787  int RearTVPos = AV.at(0).RearStartOrRepeatMins;
19788  AnsiString FrontLocName = AV.at(0).LocationName;
19789  int NextEntryPos, NextExitPos;
19790  ThisElement = Track->TrackElementAt(1395, FrontTVPos);
19791  int ThisExitPos;
19792  if(ThisElement.Conn[0] == RearTVPos)
19793  {
19794  ThisExitPos = 1;
19795  }
19796  else if(ThisElement.Conn[1] == RearTVPos)
19797  {
19798  ThisExitPos = 0;
19799  }
19800  else if(ThisElement.Conn[2] == RearTVPos)
19801  {
19802  ThisExitPos = 3;
19803  }
19804  else if(ThisElement.Conn[3] == RearTVPos)
19805  {
19806  ThisExitPos = 2;
19807  }
19808  if((ThisElement.TrackType == Buffers) && (ThisExitPos == 0))//pos 0 is the buffer
19809  {
19810  BufferFlag = true;
19811  }
19812  else //continue tracking forwards
19813  {
19814  while(true)
19815  {
19816  if(ThisElement.Conn[ThisExitPos] == -1)
19817  {
19818  SequenceLog = "13b\n";
19819  throw Exception("ThisElement connects to -1 for " + TDE.ServiceReference);
19820  }
19821  NextElement = Track->TrackElementAt(1396, ThisElement.Conn[ThisExitPos]);
19822  NextEntryPos = ThisElement.ConnLinkPos[ThisExitPos];
19823  if((NextElement.TrackType == Points) && ((NextEntryPos == 0) || (NextEntryPos == 2))) //leading points
19824  {
19825  BufferFlag = false; //should already be false
19826  break;
19827  }
19828  else if(NextElement.TrackType == Continuation)
19829  {
19830  BufferFlag = false;
19831  break;
19832  }
19833  else if((NextElement.ActiveTrackElementName != "") && (NextElement.ActiveTrackElementName != FrontLocName))
19834  {
19835  BufferFlag = false;
19836  break;
19837  }
19838  else if(NextElement.TrackType == Buffers)
19839  {
19840  BufferFlag = true;
19841  break;
19842  }
19843  else if((NextElement.TrackType == Points) && ((NextEntryPos == 1) || (NextEntryPos == 3))) //trailing points
19844  {
19845  ThisElement = NextElement;
19846  ThisExitPos = 0;
19847  continue;
19848  }
19849  else
19850  {
19851  if(NextEntryPos == 0)
19852  {
19853  NextExitPos = 1;
19854  }
19855  else if(NextEntryPos == 1)
19856  {
19857  NextExitPos = 0;
19858  }
19859  else if(NextEntryPos == 2)
19860  {
19861  NextExitPos = 3;
19862  }
19863  else if(NextEntryPos == 3)
19864  {
19865  NextExitPos = 2;
19866  }
19867  }
19868  ThisElement = NextElement;
19869  ThisExitPos = NextExitPos;
19870  }
19871  }
19872  if(BufferFlag)
19873  {
19874  if(BufferFacingUnReportedFlag)
19875  {
19876  TTFile3 << "Train facing direction on creation analysis:-\n\n";
19877  BufferFacingUnReportedFlag = false;
19878  }
19879  if(AV.at(1).Command != "cdt") //added at v2.19.0
19880  {
19881  TTFile3 << "Service " + TDE.ServiceReference + " facing buffers on creation with no immediate change of direction\n";
19882  TrainFacingBuffersReported = true; //added at v2.19.0
19883  }
19884  }
19885  }
19886  }
19887  if(!TrainFacingBuffersReported) //added at v2.19.0
19888  {
19889  TTFile3 << "Nothing to report for train facing directions\n\n";
19890  }
19891  else
19892  {
19893  TTFile3 << '\n';
19894  }
19895  SequenceLog += "13c\n";
19896 
19897  //Perform the missing cdt check. Check every entry similar to the check in SecondPassActions and if find any print out the full sequence and service entries
19898  AnsiString LocationNameToBeChecked = "";
19899  bool MissingcdtUnreportedFlag = true;
19900  TNumList MarkerList;
19901  for(unsigned int x = 0; x < SingleServiceVector.size(); x++)
19902  {
19903  const TTrainDataEntry &TDEntry = SingleServiceVector.at(x);
19904  unsigned int y = 0;
19905  int FirstInstance = 9999, SecondInstance = 9999; //9999 ensures won't be marked if not changed
19906  bool FullBreak = false;
19907  MarkerList.clear();
19908  // first discard unlocated Snt entries as they don't have location name set
19909  while((y < TDEntry.ActionVector.size()) && !FullBreak)
19910  // need to check each location name separately in turn, skipped for SignallerControl entries
19911  {
19912  if(/*(TDEntry.ActionVector.at(y).Command == "Fer") || */(TDEntry.ActionVector.at(y).FormatType == Repeat) ||
19913  (TDEntry.ActionVector.at(y).Command == "Fjo") || (TDEntry.ActionVector.at(y).Command == "Frh") ||
19914  (TDEntry.ActionVector.at(y).Command == "Frh-sh"))
19915  {
19916  break; // out of the 'while' loop since have reached the end
19917  }
19918  LocationNameToBeChecked = ""; //this section where continuation name assigned for unlocated Snts added at v2.18.0
19919  if((TDEntry.ActionVector.at(y).Command == "Snt") && (TDEntry.ActionVector.at(y).LocationType == EnRoute)) //unlocated Snt - check if the continuation has a name
19920  {
19921  int EntryPos = TDEntry.ActionVector.at(0).RearStartOrRepeatMins; //this is a track vector position
19922  LocationNameToBeChecked = Track->TrackElementAt(1678, EntryPos).ActiveTrackElementName;
19923  }
19924  if(LocationNameToBeChecked == "")
19925  {
19926  if(y == 0) //unlocated and un-named Snt, so skip this value of y
19927  {
19928  y++;
19929  continue;
19930  }
19931  LocationNameToBeChecked = TDEntry.ActionVector.at(y).LocationName; //the only un-named values for ActionVectorEntry::LocationName
19932  } //are for unlocated Snts and Fers
19933  FirstInstance = y;
19934  for(unsigned int z = y; z < TDEntry.ActionVector.size(); z++)
19935  {
19936  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(z);
19937  if(/*(AVEntry.Command == "Fer") || */(AVEntry.FormatType == Repeat) ||
19938  (AVEntry.Command == "Fjo") || (AVEntry.Command == "Frh") ||
19939  (AVEntry.Command == "Frh-sh"))
19940  {
19941  break; // out of the 'z' loop since have reached the end & 'Fer' & 'Repeat' have no location name set
19942  }
19943  if(AVEntry.Command == "cdt")
19944  {
19945  break; // out of the 'z' loop since the check is only valid up to a change of direction
19946  }
19947  if(AVEntry.LocationName == LocationNameToBeChecked)
19948  {
19949  continue; // keep going while name same
19950  }
19951  if(AVEntry.LocationName != LocationNameToBeChecked)
19952  // if name different check forwards to see if repeats
19953  {
19954  for(unsigned int a = z; a < TDEntry.ActionVector.size(); a++)
19955  {
19956  AnsiString LocationName;
19957  if(TDEntry.ActionVector.at(a).Command == "cdt")
19958  {
19959  break; // out of the 'a' & 'z' loops since the check is only valid up to a change of direction
19960  }
19961  if(TDEntry.ActionVector.at(a).Command == "Fer") //this section where continuation name assigned for Fers added at v2.18.0
19962  {
19963  int ExitLoc = TDEntry.ActionVector.at(a).ExitList.front();
19964  LocationName = Track->TrackElementAt(1679, ExitLoc).ActiveTrackElementName;
19965  }
19966  else
19967  {
19968  LocationName = TDEntry.ActionVector.at(a).LocationName;
19969  }
19970  if(LocationName == LocationNameToBeChecked)
19971  {
19972  SecondInstance = a;
19973  AnsiString Sequence = TDEntry.ServiceReference;
19974  for(unsigned int b = 0; b < TDEntry.ActionVector.size(); b++)
19975  {
19976  if(TDEntry.ActionVector.at(b).Command.SubString(1,3) == "chr")
19977  {
19978  Sequence = Sequence + AnsiString(" -> ") + TDEntry.ActionVector.at(b).OtherHeadCode;
19979  }
19980  }
19981  if(MissingcdtUnreportedFlag)
19982  {
19983  TTFile3 << "Possibly missing changes of direction - these will be missing unless the service travels in a loop back to the locations marked:-\n\n";
19984  }
19985  TTFile3 << LocationNameToBeChecked << " is listed twice with no direction change between in service sequence: " << Sequence << "\n\n";
19986  MarkerList.push_back(FirstInstance);
19987  MarkerList.push_back(SecondInstance);
19988  SingleServiceOutput(0, x, MarkerList, SingleServiceVector, TTFile3);
19989  MissingcdtUnreportedFlag = false;
19990  FullBreak = true; //no more checks for this sequence
19991  break; //out of the a & z loops
19992  }
19993  }
19994  break; // out of the 'z' loop since have checked 'a' as far as need to
19995  }
19996  }
19997  y++;
19998  }
19999  }
20000  if(MissingcdtUnreportedFlag)
20001  {
20002  TTFile3 << "Nothing to report for missing changes of direction\n\n";
20003  }
20004  else
20005  {
20006  TTFile3 << '\n';
20007  }
20008  SequenceLog += "14\n";
20009 
20010 /* Perform the questionable cdt check. Examine each service in the SingleServiceVector, and if don't find the same
20011  name either side of a cdt (before the next cdt) then flag as a questionable cdt.
20012  Method: Have an outer loop for each service that looks for cdts. When found work backwards to the last cdt or beginning and std::list all the
20013  locations excluding the cdt location. Then work forwards to the next cdt or the end and do the same. Sort each list and make unique (duplicated
20014  names on one side of a cdt already checked either by the tt validator or the missing cdt check). Then compare the two lists and if any location
20015  included in both then ok, else report as questionable. If one list is empty then it is reported.
20016 */
20017  typedef std::list<AnsiString> TLocList;
20018  TLocList BackwardList, ForwardList;
20019  bool IntroLineNeeded = true;
20020  for(unsigned int x = 0; x < SingleServiceVector.size(); x++)
20021  {
20022  unsigned int cdtPosition = 9999;
20023  AnsiString cdtLocation = "";
20024  bool FoundSameName = false;
20025  bool FerEntry = false;
20026  MarkerList.clear();
20027  const TTrainDataEntry &TDEntry = SingleServiceVector.at(x);
20028  for(unsigned int y = 0; y <= TDEntry.ActionVector.size(); y++) // <= because need to examine Fer endings after reached end of vector
20029  // need to check each location name separately in turn, skipped for SignallerControl entries
20030  {
20031  if((y == TDEntry.ActionVector.size()) && !FerEntry)
20032  {
20033  break;
20034  }
20035  BackwardList.clear();
20036  ForwardList.clear();
20037  bool ValidEnd = false;
20038  if(y < TDEntry.ActionVector.size())
20039  {
20040  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(y);
20041  if((AVEntry.FormatType == Repeat) || //end of SSVector, shouldn't be any repeats
20042  (AVEntry.Command == "Fjo") || (AVEntry.Command == "Frh") ||
20043  (AVEntry.Command == "Frh-sh"))
20044  {
20045  ValidEnd = true;
20046  }
20047  }
20048  if(FerEntry || ValidEnd)
20049  {
20050  if(MarkerList.empty())
20051  {
20052  break; // out of the 'y' loop since have reached the end & nothing to report
20053  }
20054  else
20055  {
20056  AnsiString Sequence = TDEntry.ServiceReference;
20057  for(unsigned int b = 0; b < TDEntry.ActionVector.size(); b++)
20058  {
20059  if(TDEntry.ActionVector.at(b).Command.SubString(1,3) == "chr")
20060  {
20061  Sequence = Sequence + AnsiString(" -> ") + TDEntry.ActionVector.at(b).OtherHeadCode;
20062  }
20063  }
20064  MarkerList.sort();
20065  if(IntroLineNeeded)
20066  {
20067  TTFile3 << "Questionable change of direction analysis.\n\n";
20068  TTFile3 << "For marked changes of direction there are no same-name locations listed both above (up to the start or another direction change)\n";
20069  TTFile3 << "and below (down to the end or another direction change) but not counting the change of direction location itself.\n\n";
20070  TTFile3 << "These changes of direction are probably valid for movements to and from depots but all should be checked to\n";
20071  TTFile3 << "make sure that none has been included incorrectly:\n\n";
20072  IntroLineNeeded = false;
20073  }
20074  TTFile3 << "Service sequence " << Sequence << " contains questionable changes of direction:-\n\n";
20075  SingleServiceOutput(1, x, MarkerList, SingleServiceVector, TTFile3);
20076  break;
20077  }
20078  }
20079  if(y < TDEntry.ActionVector.size()) //if it is == ...size() then shouldn't have reached here
20080  {
20081  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(y);
20082  if(AVEntry.Command != "cdt")
20083  {
20084  continue; //only looking for cdts
20085  }
20086  //here have found a cdt
20087  cdtPosition = y;
20088  cdtLocation = AVEntry.LocationName;
20089  for(int z = y - 1; z >= 0; z--)
20090  {
20091  const TActionVectorEntry &AVEntry2 = TDEntry.ActionVector.at(z);
20092  if(AVEntry2.Command == "cdt")
20093  {
20094  break; //don't look further back than the last cdt
20095  }
20096  AnsiString LocName = ""; //this section where continuation name assigned for unlocated Snts added at v2.18.0
20097  if((AVEntry2.Command == "Snt") && (AVEntry2.LocationType == EnRoute)) //unlocated Snt - check if the continuation has a name
20098  {
20099  int EntryPos = AVEntry2.RearStartOrRepeatMins; //this is a track vector position
20101  }
20102  if(LocName == "")
20103  {
20104  LocName = AVEntry2.LocationName; //the only un-named values for ActionVectorEntry::LocationName
20105  } //are for unlocated Snts and Fers
20106  if((LocName != "") && (AVEntry2.LocationName != cdtLocation)) //if an earlier entry == cdtLocation will have been picked up in an earlier check
20107  {
20108  BackwardList.push_back(LocName);
20109  }
20110  }
20111  BackwardList.sort();
20112  BackwardList.unique();
20113  for(unsigned int z = y + 1; z < TDEntry.ActionVector.size(); z++)
20114  {
20115  const TActionVectorEntry &AVEntry3 = TDEntry.ActionVector.at(z);
20116  if(/*(AVEntry3.Command == "Fer") || */(AVEntry3.FormatType == Repeat) ||
20117  (AVEntry3.Command == "Fjo") || (AVEntry3.Command == "Frh") ||
20118  (AVEntry3.Command == "Frh-sh") || (AVEntry3.Command == "cdt"))
20119  {
20120  break; // out of the 'z' loop since have reached another cdt or the end
20121  }
20122  AnsiString LocName = "";
20123  if(AVEntry3.Command == "Fer") //this section where continuation name assigned for Fers added at v2.18.0
20124  {
20125  int ExitLoc = AVEntry3.ExitList.front();
20126  LocName = Track->TrackElementAt(1681, ExitLoc).ActiveTrackElementName;
20127  FerEntry = true;
20128  }
20129  else
20130  {
20131  LocName = AVEntry3.LocationName;
20132  }
20133  if((LocName != "") && (AVEntry3.LocationName != cdtLocation)) //if a later entry == cdtLocation will have been picked up in an earlier check
20134  {
20135  ForwardList.push_back(LocName);
20136  }
20137  }
20138  ForwardList.sort();
20139  ForwardList.unique();
20140  FoundSameName = false;
20141  //now have both lists compiled (may be empty) so check for same name in both & report if don't find any
20142  if(!BackwardList.empty() && !ForwardList.empty())
20143  {
20144  for(TLocList::iterator BLIt = BackwardList.begin(); BLIt != BackwardList.end(); BLIt++)
20145  {
20146  for(TLocList::iterator FLIt = ForwardList.begin(); FLIt != ForwardList.end(); FLIt++)
20147  {
20148  if(*BLIt == *FLIt)
20149  {
20150  FoundSameName = true;
20151  }
20152  }
20153  }
20154  }
20155  if(!FoundSameName) //report the inability to find same name
20156  {
20157  MarkerList.push_back(cdtPosition);
20158  }
20159  }
20160  }
20161  }
20162  if(IntroLineNeeded)
20163  {
20164  TTFile3 << "Nothing to report for questionable changes of direction\n\n";
20165  }
20166  else
20167  {
20168  TTFile3 << '\n';
20169  }
20170 /*
20171 //debug section
20172 //print all SSVector for diagnostic purposes
20173  TTFile3 << "Whole SSVector\n\n";
20174  TNumList EmptyList;
20175  for(unsigned int x = 0; x < SingleServiceVector.size(); x++)
20176  {
20177  SingleServiceOutput(, x, EmptyList, SingleServiceVector, TTFile3);
20178  }
20179 //end of debug section
20180 */
20181  }
20182  SequenceLog += "15\n";
20183  TTFile3.close();
20184  Utilities->CallLogPop(2212);
20185  return(true);
20186  }
20187 
20188  catch(const Exception &e) //non error catch
20189  {
20190  AnsiString TTErrorFileName = "Analysis Error.txt";
20191  TTErrorFileName = CurDir + "\\Formatted timetables\\" + TTErrorFileName;
20192  std::ofstream TTError(TTErrorFileName.c_str());
20193  if(TTError == 0)
20194  {
20195  ShowMessage("Analysis error file failed to open - can't be created");
20196  Utilities->CallLogPop(2233);
20197  return(false);
20198  }
20199  AnsiString TimeNow = TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss");
20200  TTError << TimeNow.c_str() << '\n' << ArrRange << '\n' << ArrChecked << '\n' << DepRange << '\n' <<
20201  DepChecked << '\n' << AtLocChecked << '\n' << SequenceLog << '\n' << AnsiString(e.Message);
20202 
20203  TTError.close();
20204  ShowMessage("Error in Conflict Analysis: A file called 'Analysis Error.txt' has been created in your Formatted timetables folder. Please send this file together with your railway and timetable files to railwayfeedback@gmail.com for investigation - many thanks");
20205  Utilities->CallLogPop(2226);
20206  return(false);
20207  }
20208 }
20209 
20210 // ---------------------------------------------------------------------------
20211 void TTrainController::SingleServiceOutput(int Caller, int SSVectorNumber, TNumList MarkerList, TTrainDataVector &SingleServiceVector, std::ofstream &VecFile)
20212 {
20213  Utilities->CallLog.push_back(Utilities->TimeStamp() + AnsiString(SSVectorNumber) + ',' + ",SingleServiceOutput");
20214  if((SSVectorNumber < 0) || ((unsigned int)SSVectorNumber >= SingleServiceVector.size()))
20215  {
20216  throw Exception("SSVectorNumber out of range, = " + AnsiString(SSVectorNumber) + ", Vector size = " + SingleServiceVector.size());
20217  }
20218  TTrainDataEntry SingleService = SingleServiceVector.at(SSVectorNumber);
20219  {
20220  VecFile << ",Initial service reference " << SingleService.ServiceReference + '\n';
20221  AnsiString Marker = "";
20222  for(unsigned int x = 0; x < SingleService.ActionVector.size(); x++)
20223  {
20224  Marker = ',';
20225  for(TNumListIterator MLIt = MarkerList.begin(); MLIt != MarkerList.end(); MLIt++)
20226  {
20227  if(int(x) == *MLIt)
20228  {
20229  Marker = "-->,";
20230  break;
20231  }
20232  }
20233  TActionVectorEntry AVE = SingleService.ActionVector.at(x);
20234  if(AVE.FormatType == StartNew)
20235  {
20236  AnsiString RearID = Track->TrackElementAt(1397, AVE.RearStartOrRepeatMins).ElementID;
20237  AnsiString FrontID = Track->TrackElementAt(1398, AVE.FrontStartOrRepeatDigits).ElementID;
20238  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << RearID << ' ' << FrontID << '\n';
20239  }
20240  if(AVE.FormatType == SNTShuttle)
20241  {
20242  AnsiString RearID = Track->TrackElementAt(1399, AVE.RearStartOrRepeatMins).ElementID;
20243  AnsiString FrontID = Track->TrackElementAt(1400, AVE.FrontStartOrRepeatDigits).ElementID;
20244  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << RearID << ' ' << FrontID << ' ' << AVE.OtherHeadCode << '\n';
20245  }
20246  if(AVE.FormatType == SNSShuttle) //should all have been converted to chr
20247  {
20248  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << AVE.OtherHeadCode << ' ' << AVE.NonRepeatingShuttleLinkHeadCode << '\n';
20249  }
20250  if(AVE.FormatType == Repeat) //shouldn't be any repeats, only here to show if any have been copied
20251  {
20252  VecFile << Marker << "Repeat " << AVE.RearStartOrRepeatMins << ' ' << AVE.FrontStartOrRepeatDigits << ' ' << AVE.NumberOfRepeats << '\n';
20253  }
20254  if((AVE.FormatType == TimeCmd) || (AVE.FormatType == TimeCmdHeadCode) || (AVE.FormatType == FNSNonRepeatToShuttle) || (AVE.FormatType == FSHNewService))
20255  {
20256  TActionVectorEntry AVHolder = AVE;
20257  if(AVE.Command.SubString(1,3) == "chr")
20258  {
20259  if(AVE.Command.SubString(5, AVE.Command.Length() - 4) == "sp")
20260  {
20261  AVE.Command = "Change of service to " + AVE.OtherHeadCode + " after split";
20262  AVE.OtherHeadCode = "";
20263  }
20264  if(AVE.Command.SubString(5, AVE.Command.Length() - 4) == "Fns")
20265  {
20266  AVE.Command = "Change of service to ";
20267  }
20268  if(AVE.Command.SubString(5, AVE.Command.Length() - 4) == "Fns-sh")
20269  {
20270  AVE.Command = "Change to shuttle finishing service";
20271  }
20272  if(AVE.Command.SubString(5, AVE.Command.Length() - 4) == "F-nshs")
20273  {
20274  AVE.Command = "Change to shuttle service " + AVE.OtherHeadCode + " from feeder";
20275  AVE.OtherHeadCode = "";
20276  }
20277  }
20278  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << AVE.OtherHeadCode << '\n';
20279  AVE = AVHolder;
20280  }
20281  else if((AVE.FormatType == TimeLoc) && (AVE.ArrivalTime != TDateTime(-1)))
20282  {
20283  VecFile << Marker << Utilities->Format96HHMM(AVE.ArrivalTime) << " Arr " << AVE.LocationName << '\n';
20284  }
20285  else if((AVE.FormatType == TimeLoc) && (AVE.DepartureTime != TDateTime(-1)))
20286  {
20287  VecFile << Marker << Utilities->Format96HHMM(AVE.DepartureTime) << " Dep " << AVE.LocationName << '\n';
20288  }
20289  else if(AVE.FormatType == TimeTimeLoc)
20290  {
20291  VecFile << Marker << Utilities->Format96HHMM(AVE.ArrivalTime) << ' ' << Utilities->Format96HHMM(AVE.DepartureTime) << ' ' << AVE.LocationName << '\n';
20292  }
20293  else if(AVE.FormatType == PassTime)
20294  {
20295  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << "Pass" << ' ' << AVE.LocationName << '\n';
20296  }
20297  else if(AVE.FormatType == ExitRailway) //ListOfExits added at v2.10.0
20298  {
20299  AnsiString ListOfExits = "";
20300  for(TNumListIterator NLIt = AVE.ExitList.begin(); NLIt != AVE.ExitList.end(); NLIt++)
20301  {
20302  ListOfExits += AnsiString(Track->TrackElementAt(1432, *NLIt).ElementID) + ' ';
20303  }
20304  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << " Fer " << ListOfExits <<'\n';
20305  }
20306  else if(AVE.FormatType == FinRemHere)
20307  {
20308  VecFile << Marker << "Frh" << '\n';
20309  }
20310  }
20311  VecFile << '\n';
20312  }
20313  Utilities->CallLogPop(2318);
20314 }
20315 
20316 // ---------------------------------------------------------------------------
20317 
20318 TTrainDataEntry TTrainController::GetServiceFromVector(AnsiString Caller, AnsiString HeadCode, TTrainDataVector Vector, bool &FinishType, bool &FoundFlag)
20319 {
20320  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetServiceFromVector," + HeadCode);
20321  FoundFlag = false;
20322  FinishType = true;
20323  for(unsigned int x = 0; x < Vector.size(); x++)
20324  {
20325 // AnsiString ThisRef = Vector.at(x).ServiceReference;
20326  if(Vector.at(x).ServiceReference == HeadCode)
20327  {
20328  if(Vector.at(x).ActionVector.at(Vector.at(x).ActionVector.size() - 1).FormatType == Repeat) //shouldn't be any repeats
20329  {
20330  TActionVectorEntry AVE = Vector.at(x).ActionVector.at(Vector.at(x).ActionVector.size() - 2);
20331  if((AVE.Command == "Fjo") || (AVE.Command == "Frh") || (AVE.Command == "Fer") || (AVE.Command == "Frh-sh"))
20332  {
20333  FinishType = false;
20334  }
20335  }
20336  else
20337  {
20338  TActionVectorEntry AVE = Vector.at(x).ActionVector.at(Vector.at(x).ActionVector.size() - 1);
20339  if((AVE.Command == "Fjo") || (AVE.Command == "Frh") || (AVE.Command == "Fer") || (AVE.Command == "Frh-sh"))
20340  {
20341  FinishType = false;
20342  }
20343  }
20344  FoundFlag = true;
20345  Utilities->CallLogPop(2319);
20346  return(Vector.at(x));
20347  }
20348  }
20349  Utilities->CallLogPop(2320);
20350  return(Vector.at(Vector.size() - 1)); //return last for want of returning something
20351 }
20352 
20353 // ---------------------------------------------------------------------------
20354 
20355 bool TTrainController::WithinTimeRange(int Caller, AnsiString Time1, AnsiString Time2, int MinuteRange) //times are "HH:MM"
20356 {
20357 //convert times to integer minutes
20358  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",WithinTimeRange," + Time1 + "," + Time2 + "," + AnsiString(MinuteRange));
20359  if((Time1 == "") || (Time2 == ""))
20360  {
20361  Utilities->CallLogPop(2213);
20362  return(false);
20363  }
20364  int Mins = Time1.SubString(4,2).ToInt();
20365  int Hours = Time1.SubString(1,2).ToInt();
20366  int Time1Mins = (Hours * 60) + Mins;
20367  Mins = Time2.SubString(4,2).ToInt();
20368  Hours = Time2.SubString(1,2).ToInt();
20369  int Time2Mins = (Hours * 60) + Mins;
20370  if(abs(Time1Mins - Time2Mins) <= MinuteRange)
20371  {
20372  Utilities->CallLogPop(2214);
20373  return(true);
20374  }
20375  Utilities->CallLogPop(2215);
20376  return(false);
20377 }
20378 
20379 // ---------------------------------------------------------------------------
20380 
20381 AnsiString TTrainController::ConsolidateSARNTArrDep(int Caller, const AnsiString Input, int &NumTrainsAtLoc, AnsiString Location, bool Arrival,
20382  bool &AnalysisError, int &MaxNumberOfSameDirections)
20383 {
20384  //input consists of services and service Arr or Dep times as a comma separated list, Location needed to determine direction information
20385 
20386  try
20387  {
20388  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ConsolidateSARNTArrDep," + Input);
20389  AnsiString Output = "", OneService = "", TempStr1 = "", TempStr2 = "";
20390  int SCPos = 0;
20391  std::list<AnsiString> ServiceList; //this is the list of services with times extracted from Input - not to be confused with ServiceCallingPointsList
20392  //first change every second comma in Input to a semicolon so can separate services but keep times with services
20393  bool EvenComma = false;
20394  for(int x = 1; x <= Input.Length(); x++)
20395  {
20396  TempStr1 = Input[x];
20397  if(TempStr1 == AnsiString(',') && EvenComma)
20398  {
20399  TempStr2 += ';';
20400  }
20401  else
20402  {
20403  TempStr2 += Input[x];
20404  }
20405  if(TempStr1 == AnsiString(','))
20406  {
20407  EvenComma = !EvenComma;
20408  }
20409  }
20410  //load up the list of services with associated times
20411  while(TempStr2.Length() > 0)
20412  {
20413  SCPos = TempStr2.Pos(';');
20414  if(SCPos > 0) //0 if not found, as won't be when only one service left
20415  {
20416  OneService = TempStr2.SubString(1, SCPos - 1);
20417  ServiceList.push_back(OneService);
20418  TempStr2 = TempStr2.SubString(SCPos + 1, TempStr2.Length() - SCPos);
20419  }
20420  else //no semicolon so looking at last (or only) element
20421  {
20422  ServiceList.push_back(TempStr2);
20423  TempStr2 = "";
20424  }
20425  }
20426  ServiceList.sort(); // alphabetical order
20427  ServiceList.unique(); //remove duplicates
20428  NumTrainsAtLoc = ServiceList.size(); //calc this after removing duplicates as may not have changed
20429 
20430  //now add direction information from AllServiceCallingLocsMap - key is service ref and value a list of calling points in order
20431  int DirectionMarker = 0; //this is added in & runs from 1 upwards, same marker for diff services = same direction
20432  //first add the direction marker "&0" for not yet allocated - '&' is an identifier
20433  std::list<AnsiString>::iterator SLIt, SLIt1, SLIt2, SLIt3;
20434 
20435  for(SLIt = ServiceList.begin(); SLIt != ServiceList.end(); SLIt++)
20436  {
20437  *SLIt = *SLIt + "&0"; //add in a basic direction marker to each service
20438  }
20439  SLIt3 = ServiceList.end();
20440  SLIt3--; //so points to last element
20441  AnsiString ServiceRef1, ServiceRef2, AnsiTime1, AnsiTime2, RepeatInfo1, RepeatInfo2; //1 refers to first for..next loop & 2 to second
20442  int AmpersandPos, SpacePos, CommaPos1, CommaPos2, RepeatNum1, RepeatNum2;
20443  TAllServiceCallingLocsMap::iterator ASCLIt1, ASCLIt2;
20444  TServiceCallingLocsList ServiceCallingLocsList1, ServiceCallingLocsList2;
20445  MaxNumberOfSameDirections = 0; //at end of each SLIt loop if SameDirectionCount > MaxNumberOfSameDirections then MaxNumberOfSameDirections = SameDirectionCount
20446  int SameDirectionCount = 0; //starts at 1 at each SLIt loop (because SLIt1 entry already has a DirectionMarker) and increments for every same direction
20447 
20448  for(std::list<AnsiString>::iterator SLIt1 = ServiceList.begin(); SLIt1 != SLIt3; SLIt1++) //should be end() - 1 but can't use -1 with lists so have to improvise
20449  {
20450  SLIt = SLIt1;
20451  SLIt++; //so points to one after SLIt1
20452  if(SLIt1->SubString(SLIt1->Length() - 1, 2) != AnsiString("&0"))
20453  {
20454  continue; //already allocated so skip to the next
20455  }
20456  else
20457  {
20458  CommaPos1 = SLIt1->Pos(','); //can't be 0
20459  ServiceRef1 = SLIt1->SubString(1, CommaPos1 - 1);
20460  //but this contains "(First service..." etc so need to strip these, but use to extract RepeatNum
20461  SpacePos = ServiceRef1.Pos(' ');
20462  RepeatNum1 = 0;
20463  if(SpacePos > 0) //otherwise it's already correct
20464  {
20465  RepeatInfo1 = ServiceRef1.SubString(SpacePos + 2, ServiceRef1.Length() - SpacePos - 2); //drops the brackets and leaves "First service", "Repeat 2" etc
20466  ServiceRef1 = ServiceRef1.SubString(1, SpacePos - 1);
20467  if(RepeatInfo1[1] == 'F')
20468  {
20469  RepeatNum1 = 0;
20470  }
20471  else
20472  {
20473  SpacePos = RepeatInfo1.Pos(' ');
20474  RepeatNum1 = RepeatInfo1.SubString(SpacePos + 1, RepeatInfo1.Length() - SpacePos).ToInt();
20475  }
20476  }
20477  AnsiTime1 = SLIt1->SubString(CommaPos1 + 1, SLIt1->Length() - CommaPos1);
20478  //but this includes the "&0" etc so need to strip these
20479  AmpersandPos = AnsiTime1.Pos('&');
20480  AnsiTime1 = AnsiTime1.SubString(1, AmpersandPos - 1);
20481 
20482  ASCLIt1 = AllServiceCallingLocsMap.find(ServiceRef1);
20483  if(ASCLIt1 == AllServiceCallingLocsMap.end()) //can't find it
20484  {
20485  throw Exception("ASCLIt1 Error in " + Input);
20486  }
20487  ServiceCallingLocsList1 = ASCLIt1->second;
20488  AmpersandPos = SLIt1->Pos('&');
20489  *SLIt1 = SLIt1->SubString(1, AmpersandPos); //truncate up to & leave in the '&'
20490  *SLIt1 = *SLIt1 + AnsiString(++DirectionMarker); //now add the next marker (pre-increment), allow for it being more than one digit
20491 
20492  SameDirectionCount = 1;
20493  for(SLIt2 = SLIt; SLIt2 != ServiceList.end(); SLIt2++)
20494  {
20495  CommaPos2 = SLIt2->Pos(','); //can't be 0
20496  ServiceRef2 = SLIt2->SubString(1, CommaPos2 - 1);
20497  //but this contains "(First service..." etc so need to strip these
20498  SpacePos = ServiceRef2.Pos(' ');
20499  RepeatNum2 = 0;
20500  if(SpacePos > 0) //otherwise it's already correct
20501  {
20502  RepeatInfo2 = ServiceRef2.SubString(SpacePos + 2, ServiceRef2.Length() - SpacePos - 2); //drops the brackets and leaves "First service", "Repeat 2" etc
20503  ServiceRef2 = ServiceRef2.SubString(1, SpacePos - 1);
20504  if(RepeatInfo2[1] == 'F')
20505  {
20506  RepeatNum2 = 0;
20507  }
20508  else
20509  {
20510  SpacePos = RepeatInfo2.Pos(' ');
20511  RepeatNum2 = RepeatInfo2.SubString(SpacePos + 1, RepeatInfo2.Length() - SpacePos).ToInt();
20512  }
20513  }
20514  AnsiTime2 = SLIt2->SubString(CommaPos2 + 1, SLIt2->Length() - CommaPos2);
20515  //but this includes the "&0" etc so need to strip these
20516  AmpersandPos = AnsiTime2.Pos('&');
20517  AnsiTime2 = AnsiTime2.SubString(1, AmpersandPos - 1);
20518 
20519  ASCLIt2 = AllServiceCallingLocsMap.find(ServiceRef2);
20520  if(ASCLIt2 == AllServiceCallingLocsMap.end()) //can't find it
20521  {
20522  throw Exception("ASCLIt2 Error in " + Input);
20523  }
20524  ServiceCallingLocsList2 = ASCLIt2->second;
20525  //now compare the two
20526  if(SameDirection(0, ServiceRef1, ServiceRef2, AnsiTime1, AnsiTime2, RepeatNum1, RepeatNum2, ServiceCallingLocsList1, ServiceCallingLocsList2, Location, Arrival))
20527  {
20528  int AmpersandPos = SLIt2->Pos('&');
20529  *SLIt2 = SLIt2->SubString(1, AmpersandPos); //truncate up to & leave in the '&'
20530  *SLIt2 = *SLIt2 + AnsiString(DirectionMarker); //now add the same marker as *SLIt1
20531  SameDirectionCount++;
20532  }
20533  }
20534  if(SameDirectionCount > MaxNumberOfSameDirections)
20535  {
20536  MaxNumberOfSameDirections = SameDirectionCount;
20537  }
20538  }
20539  }
20540 
20541  if(SLIt3->SubString(SLIt3->Length() - 1, 2) == AnsiString("&0")) //*SLTIt3 is the last in the list and may not have been allocated, if not it doesn't match
20542  {
20543  //any existing direction so allocate it now
20544  AmpersandPos = SLIt3->Pos('&');
20545  *SLIt3 = SLIt3->SubString(1, AmpersandPos); //truncate up to & leave in the '&'
20546  *SLIt3 = *SLIt3 + AnsiString(++DirectionMarker);
20547  }
20548  //now change direction markers to upper case letters beginning with 'A' (and continuing with 'AA' if exceed 26) & add a comma before so have ServiceRef, DirectionMarker, Time
20549  for(SLIt = ServiceList.begin(); SLIt != ServiceList.end(); SLIt++)
20550  {
20551  //extract the DirectionMarker as an integer
20552  AmpersandPos = SLIt->Pos('&');
20553  AnsiString DirectionMarkerString = SLIt->SubString(AmpersandPos + 1, SLIt->Length() - AmpersandPos); //extract the number as an ansistring
20554  AnsiString ServiceWithoutMarker = SLIt->SubString(1, AmpersandPos - 1); //truncate the &number
20555  DirectionMarker = DirectionMarkerString.ToInt();
20556  AnsiString DirectionSuffix = "";
20557  char c;
20558  if(DirectionMarker < 27)
20559  {
20560  c = 64 + DirectionMarker; //so 1 -> 'A'
20561  DirectionSuffix = "," + AnsiString(c);
20562  }
20563  else if(DirectionMarker < 53)
20564  {
20565  c = 65 + DirectionMarker - 27; //so 27 -> 'AA'
20566  DirectionSuffix = ",A" + AnsiString(c);
20567  }
20568  else
20569  {
20570  DirectionSuffix = ",?"; //shouldn'tn ever get this far!
20571  }
20572  *SLIt = ServiceWithoutMarker + DirectionSuffix;
20573  }
20574  //now prepare the final consolidated output
20575  for(SLIt = ServiceList.begin(); SLIt != ServiceList.end(); SLIt++)
20576  {
20577  Output = Output + *SLIt + ","; //will end up with an unwanted comma at the end
20578  }
20579  if(Output.Length() > 0)
20580  {
20581  Output = Output.SubString(1, Output.Length() - 1); //remove the last comma
20582  }
20583  Utilities->CallLogPop(2216);
20584  return(Output);
20585  }
20586 
20587  catch(const Exception &e) //non error catch
20588  {
20589  AnalysisError = true;
20590  Utilities->CallLogPop(2227);
20591  return(e.Message);
20592  }
20593 }
20594 
20595 // ---------------------------------------------------------------------------
20596 
20597 AnsiString TTrainController::ConsolidateSARNTAtLoc(int Caller, const AnsiString Input, int &NumTrainsAtLoc)
20598 {
20599  //similar to above but doesn't include times in the input
20600  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ConsolidateSARNTAtLoc," + Input);
20601  AnsiString InternalInput = Input, Output = "", OneService = "";
20602  int CommaPos = 0;
20603  std::list<AnsiString> ServiceList;
20604  //load up the list
20605  while(InternalInput.Length() > 0)
20606  {
20607  CommaPos = InternalInput.Pos(',');
20608  if(CommaPos > 0) //0 if not found, as won't be when only one service left
20609  {
20610  OneService = InternalInput.SubString(1, CommaPos - 1);
20611  ServiceList.push_back(OneService);
20612  InternalInput = InternalInput.SubString(CommaPos + 1, InternalInput.Length() - CommaPos);
20613  }
20614  else //no comma so looking at last (or only) element
20615  {
20616  ServiceList.push_back(InternalInput);
20617  InternalInput = "";
20618  }
20619  }
20620 
20621  ServiceList.sort(); // alphabetical order
20622  ServiceList.unique(); //remove duplicates
20623  NumTrainsAtLoc = ServiceList.size(); //calc this after removing duplicates as may not have changed
20624  for(std::list<AnsiString>::iterator SLIt = ServiceList.begin(); SLIt != ServiceList.end(); SLIt++)
20625  {
20626  Output = Output + *SLIt + ","; //will end up with an unwanted comma at the end
20627  }
20628  if(Output.Length() > 0)
20629  {
20630  Output = Output.SubString(1, Output.Length() - 1); //remove the last comma
20631  }
20632  Utilities->CallLogPop(2217);
20633  return(Output);
20634 }
20635 
20636 // ---------------------------------------------------------------------------
20637 
20638 
20639 bool TTrainController::SameDirection(int Caller, AnsiString Ref1In, AnsiString Ref2In, AnsiString Time1, AnsiString Time2, int RepeatNum1, int RepeatNum2, TServiceCallingLocsList List1,
20640  TServiceCallingLocsList List2, AnsiString Location, bool Arrival)
20641 {
20642  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SameDirection," + Ref1In + "," + Ref2In + "," + Time1 + "," + Time2 + "," +
20643  AnsiString(RepeatNum1) + "," + AnsiString(RepeatNum2) + "," + Location);
20644 
20645  std::list<AnsiString>::iterator LP1, LP2, ListPtr1, ListPtr2, LocPtr1, LocPtr2; //LP1 & 2 are temporary pointers, ListPtrs are
20646  //general list pointers, LocPtrs point to Location in the two lists
20647 
20648  //first find the relevant values for LocPtr1 & LocPtr2 taking account of cdts and times
20649  //for List1
20650  bool LocFound = false;
20651  AnsiString Ref1 = Ref1In, Ref2 = Ref2In;
20652  int IncMinutes;
20653  TDateTime FirstServiceTime;
20654 
20655  //first need to strip off /1, /2 etc if present from Ref1 & Ref2 (leave Ref1In & Ref2In for error message & retain value as target in finding the correct reference for cdts)
20656  int Ref1Target = 0, Ref1Count = 0;
20657  int Ref2Target = 0, Ref2Count = 0;
20658 
20659 /* drop this after retained slashes in ServiceRef
20660  int SlashPos = Ref1.Pos('/');
20661  if(SlashPos > 0) //if 0 Ref1 == Ref1In & target stays at 0
20662  {
20663  Ref1Target = Ref1.SubString(SlashPos + 1, Ref1.Length() - SlashPos).ToInt();
20664  Ref1 = Ref1.SubString(1, SlashPos - 1); //truncate up to but omit '/'
20665  }
20666  int Ref2Target = 0, Ref2Count = 0;
20667  SlashPos = Ref2.Pos('/');
20668  if(SlashPos > 0) //if 0 leave as is
20669  {
20670  Ref2Target = Ref2.SubString(SlashPos + 1, Ref2.Length() - SlashPos).ToInt();
20671  Ref2 = Ref2.SubString(1, SlashPos - 1); //truncate up to but omit '/'
20672  }
20673 */
20674 
20675  for(ListPtr1 = List1.begin(); ListPtr1 != List1.end(); ListPtr1++) //note that when this routine entered Ref1In & Ref2In are already set to the correct services,
20676  {
20677  //even if others have same names. But if there are cdt's then need to refind the correct service
20678  if((*ListPtr1) == Location) //
20679  {
20680  LocPtr1 = ListPtr1; //may be modified later
20681  LocFound = true;
20682  }
20683  if(ListPtr1->SubString(1, 3) == "%%%")
20684  {
20685  AnsiString CDTTime = ListPtr1->SubString(4, 5);
20686  //now adjust the time to correspond to the repeat if there is one
20687  if(RepeatNum1 > 0) //if it is 0 then AnsiTime1 is already valid
20688  {
20689  IncMinutes = -1;
20690  FirstServiceTime = TDateTime(-1);
20691  bool BreakFlag = false;
20692  for(TTrainDataVector::iterator TDVIt = TrainDataVectorCopy.begin(); TDVIt != TrainDataVectorCopy.end(); TDVIt++)
20693  {
20694  if(TDVIt->ServiceReference == Ref1)
20695  {
20696  if(Ref1Target > Ref1Count)
20697  {
20698  Ref1Count++;
20699  continue;
20700  }
20701  IncMinutes = TDVIt->ActionVector.back().RearStartOrRepeatMins;
20702  for(TActionVector::iterator AVIt = TDVIt->ActionVector.begin(); AVIt != TDVIt->ActionVector.end(); AVIt++)
20703  {
20704  if(Utilities->Format96HHMM(AVIt->EventTime) == CDTTime)
20705  {
20706  FirstServiceTime = AVIt->EventTime; //i.e. the FirstService value of CDTTime
20707  BreakFlag = true;
20708  break;
20709  }
20710  if(Utilities->Format96HHMM(AVIt->ArrivalTime) == CDTTime) //add arr & dep in case find sooner (though dep shouldn't be sooner)
20711  {
20712  FirstServiceTime = AVIt->ArrivalTime;
20713  BreakFlag = true;
20714  break;
20715  }
20716  if(Utilities->Format96HHMM(AVIt->DepartureTime) == CDTTime)
20717  {
20718  FirstServiceTime = AVIt->DepartureTime;
20719  BreakFlag = true;
20720  break;
20721  }
20722  }
20723  if(BreakFlag)
20724  {
20725  break;
20726  }
20727  }
20728  }
20729  if(IncMinutes == -1)
20730  {
20731  throw Exception("Failed to find service for ServiceRef1 in SameDirection " + Ref1In + " " + Ref2In + " " + Time1 + " " + Time2 + " " + AnsiString(RepeatNum1) + " " + AnsiString(RepeatNum2) + " " + Location);
20732  }
20733  if(FirstServiceTime == TDateTime(-1))
20734  {
20735  throw Exception("Failed to find first service time for ServiceRef1 in SameDirection " + Ref1In + " " + Ref2In + " " + Time1 + " " + Time2 + " " + AnsiString(RepeatNum1) + " " + AnsiString(RepeatNum2) + " " + Location);
20736  }
20737  CDTTime = Utilities->Format96HHMM(TrainController->GetRepeatTime(60, FirstServiceTime, RepeatNum1, IncMinutes));
20738  }
20739  if(!Arrival && (Time1 == CDTTime)) //continue if equal in case next is a departure for the Location
20740  {
20741  LocFound = false;
20742  continue;
20743  }
20744  if(Arrival && (Time1 == CDTTime)) //gone far enough so can stop
20745  {
20746  break;
20747  }
20748  if(Time1 > CDTTime) //not there yet so go on
20749  {
20750  LocFound = false;
20751  continue;
20752  }
20753  if(Time1 < CDTTime) //gone too far so can stop now
20754  {
20755  break;
20756  }
20757  }
20758  }
20759  if(!LocFound) //have to find it in both lists
20760  {
20761  Utilities->CallLogPop(2228);
20762  return( false);
20763  }
20764  //for List2
20765  LocFound = false;
20766  for(ListPtr2 = List2.begin(); ListPtr2 != List2.end(); ListPtr2++)
20767  {
20768  if((*ListPtr2) == Location)
20769  {
20770  LocPtr2 = ListPtr2; //may be modified later
20771  LocFound = true;
20772  }
20773  if(ListPtr2->SubString(1, 3) == "%%%")
20774  {
20775  AnsiString CDTTime = ListPtr2->SubString(4, 5);
20776  //now adjust the time to correspond to the repeat if there is one
20777  if(RepeatNum2 > 0) //if it is 0 then AnsiTime1 is already valid
20778  {
20779  IncMinutes = -1;
20780  FirstServiceTime = TDateTime(-1);
20781  bool BreakFlag = false;
20782  for(TTrainDataVector::iterator TDVIt = TrainDataVectorCopy.begin(); TDVIt != TrainDataVectorCopy.end(); TDVIt++)
20783  {
20784  if(TDVIt->ServiceReference == Ref2)
20785  {
20786  if(Ref2Target > Ref2Count)
20787  {
20788  Ref2Count++;
20789  continue;
20790  }
20791  IncMinutes = TDVIt->ActionVector.back().RearStartOrRepeatMins;
20792  for(TActionVector::iterator AVIt = TDVIt->ActionVector.begin(); AVIt != TDVIt->ActionVector.end(); AVIt++)
20793  {
20794  if(Utilities->Format96HHMM(AVIt->EventTime) == CDTTime)
20795  {
20796  FirstServiceTime = AVIt->EventTime;
20797  BreakFlag = true;
20798  break;
20799  }
20800  if(Utilities->Format96HHMM(AVIt->ArrivalTime) == CDTTime)
20801  {
20802  FirstServiceTime = AVIt->ArrivalTime;
20803  BreakFlag = true;
20804  break;
20805  }
20806  if(Utilities->Format96HHMM(AVIt->DepartureTime) == CDTTime)
20807  {
20808  FirstServiceTime = AVIt->DepartureTime;
20809  BreakFlag = true;
20810  break;
20811  }
20812  }
20813  if(BreakFlag)
20814  {
20815  break;
20816  }
20817  }
20818  }
20819  if(IncMinutes == -1)
20820  {
20821  throw Exception("IncMinutes -1 for ServiceRef2 in SameDirection " + Ref1In + " " + Ref2In + " " + Time1 + " " + Time2 + " " + AnsiString(RepeatNum1) + " " + AnsiString(RepeatNum2) + " " + Location);
20822  }
20823  if(FirstServiceTime == TDateTime(-1))
20824  {
20825  throw Exception("First service time -1 for ServiceRef2 in SameDirection " + Ref1In + " " + Ref2In + " " + Time1 + " " + Time2 + " " + AnsiString(RepeatNum1) + " " + AnsiString(RepeatNum2) + " " + Location);
20826  }
20827  CDTTime = Utilities->Format96HHMM(TrainController->GetRepeatTime(61, FirstServiceTime, RepeatNum2, IncMinutes));
20828  }
20829  if(!Arrival && (Time2 == CDTTime)) //continue if equal in case next is a departure for the Location
20830  {
20831  LocFound = false;
20832  continue;
20833  }
20834  if(Arrival && (Time2 == CDTTime)) //gone far enough so can stop
20835  {
20836  break;
20837  }
20838  if(Time2 > CDTTime) //not there yet so go on
20839  {
20840  LocFound = false;
20841  continue;
20842  }
20843  if(Time2 < CDTTime) //gone too far so can stop now
20844  {
20845  break;
20846  }
20847  }
20848  }
20849  if(!LocFound) //have to find it in both lists, and should be found but allow for it not being
20850  {
20851  Utilities->CallLogPop(2229);
20852  return( false);
20853  }
20854  //now, for the arrival analysis, see if there is a common location before the LocPtrs & within any cdts, and if so return true, else return false
20855  //set ListPtr1 to the search start position
20856  if(Arrival)
20857  {
20858  LP1 = List1.begin();
20859  LP1--; //now points to before the first entry
20860  for(ListPtr1 = LocPtr1; ListPtr1 != LP1; ListPtr1--) //search backwards from Location
20861  {
20862  if(ListPtr1 == List1.begin())
20863  {
20864  break;
20865  }
20866  if(ListPtr1->SubString(1, 3) == "%%%") //a cdt event
20867  {
20868  ListPtr1++; //point to one past the cdt
20869  break;
20870  }
20871  }
20872  //set ListPtr2 to the search start position
20873  LP2 = List2.begin();
20874  LP2--; //now points to before the first entry
20875  for(ListPtr2 = LocPtr2; ListPtr2 != LP2; ListPtr2--)
20876  {
20877  if(ListPtr2 == List2.begin())
20878  {
20879  break;
20880  }
20881  if(ListPtr2->SubString(1, 3) == "%%%") //a cdt event
20882  {
20883  ListPtr2++; //point to one past the cdt
20884  break;
20885  }
20886  }
20887  //ListPtr1 & 2 now at search start position
20888  LP1 = ListPtr1;
20889  LP2 = ListPtr2;
20890  //now search forwards, i.e. for common locations before Location
20891  for(ListPtr1 = LP1; ListPtr1 != List1.end(); ListPtr1++)
20892  {
20893  if(ListPtr1 == LocPtr1) //reached Location without finding a common earlier location so skip to the backwards check
20894  {
20895  break;
20896  }
20897  if(ListPtr1->SubString(1, 3) == "%%%") //reached a cdt event without finding a common earlier location
20898  {
20899  break;
20900  }
20901  for(ListPtr2 = LP2; ListPtr2 != List2.end(); ListPtr2++)
20902  {
20903  if(ListPtr2 == LocPtr2) //not found common earlier location so go to the next ListPtr1
20904  {
20905  break;
20906  }
20907  if(ListPtr2->SubString(1, 3) == "%%%") //reached a cdt event without finding a common earlier location so go to the next ListPtr1
20908  {
20909  break;
20910  }
20911  if((*ListPtr1) == (*ListPtr2)) //found a common earlier location
20912  {
20913  Utilities->CallLogPop(2230);
20914  return( true);
20915  }
20916  }
20917  }
20918  }
20919 
20920  //now, for the departure analysis, reset the start positions and search locations after Location
20921 
20922  else
20923  {
20924  LP1 = LocPtr1;
20925  LP1++; //start at one past the location itself
20926  LP2 = LocPtr2;
20927  LP2++;
20928  for(ListPtr1 = LP1; ListPtr1 != List1.end(); ListPtr1++)
20929  {
20930  if(ListPtr1 == List1.end()) //reached end point so stop
20931  {
20932  break;
20933  }
20934  if(ListPtr1->SubString(1, 3) == "%%%") //reached a cdt event without finding a common location
20935  {
20936  break;
20937  }
20938  for(ListPtr2 = LP2; ListPtr2 != List2.end(); ListPtr2++)
20939  {
20940  if(ListPtr2 == List2.end()) //reached end point so go to next ListPtr1
20941  {
20942  break;
20943  }
20944  if(ListPtr2->SubString(1, 3) == "%%%") //reached a cdt event without finding a common location so go to the next ListPtr1
20945  {
20946  break;
20947  }
20948  if((*ListPtr1) == (*ListPtr2)) //found a common later location
20949  {
20950  Utilities->CallLogPop(2231);
20951  return( true);
20952  }
20953  }
20954  }
20955  }
20956  Utilities->CallLogPop(2232);
20957  return( false);
20958 }
20959 
20960 // ---------------------------------------------------------------------------
20961 
20962 AnsiString TTrainController::GetExitLocationAndAt(int Caller, TNumList &ExitList, AnsiString &AllowedExits) const
20963 {
20964  // changed at v2.7.0 to show allowable exit elements
20965  if(ExitList.empty())
20966  {
20967  return("");
20968  }
20969  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetExitLocationAndAt");
20970  AnsiString StartName = Track->TrackElementAt(735, *(ExitList.begin())).ActiveTrackElementName;
20971  AnsiString ExitLocList = "";
20972  AllowedExits = "";
20973 
20974  unsigned int Counter = 0;
20975  for(TNumListIterator ELIt = ExitList.begin(); ELIt != ExitList.end(); ELIt++)
20976  {
20977  ExitLocList += Track->TrackElementAt(1018, *ELIt).ElementID + " ";
20978  Counter++;
20979  if(((Counter % 6) == 0) && (Counter < (ExitList.size() - 1))) // only add a newline if more to come
20980  {
20981  ExitLocList += "\n";
20982  }
20983  }
20984  if(StartName == "")
20985  {
20986  if(ExitList.size() == 1)
20987  {
20988  AnsiString ID = Track->TrackElementAt(738, *(ExitList.begin())).ElementID;
20989  Utilities->CallLogPop(1571);
20990  return(" at " + ID);
20991  }
20992  else
20993  {
20994  Utilities->CallLogPop(1572);
20995  if(ExitList.size() < 4)
20996  {
20997  AllowedExits = ",\nallowable exit element(s): " + ExitLocList;
20998  return("");
20999  }
21000  else
21001  {
21002  AllowedExits = ",\nallowable exit element(s):\n" + ExitLocList;
21003  return("");
21004  }
21005  }
21006  }
21007  for(TNumListIterator ELIT = ExitList.begin(); ELIT != ExitList.end(); ELIT++)
21008  {
21009  if(Track->TrackElementAt(736, *ELIT).ActiveTrackElementName != StartName)
21010  {
21011  Utilities->CallLogPop(1570);
21012  if(ExitList.size() < 4)
21013  {
21014  AllowedExits = ",\nallowable exit element(s): " + ExitLocList;
21015  return("");
21016  }
21017  else
21018  {
21019  AllowedExits = ",\nallowable exit element(s):\n" + ExitLocList;
21020  return("");
21021  }
21022  }
21023  }
21024  Utilities->CallLogPop(1569);
21025  if(ExitList.size() < 4)
21026  {
21027  AllowedExits = ",\nallowable exit element(s): " + ExitLocList;
21028  return(" at " + StartName);
21029  }
21030  else
21031  {
21032  AllowedExits = ",\nallowable exit element(s):\n" + ExitLocList;
21033  return(" at " + StartName);
21034  }
21035 }
21036 
21037 // ---------------------------------------------------------------------------
21038 /* can't trust this as locations within a vector may not be contiguous
21039  bool TTrainController::IsServiceTerminating(int Caller, TTrainDataEntry *TDEPtr, TActionVectorEntry *AVPtr)
21040  {
21041  //Search ActionVector from the position after the entry value for Ptr to the end, and return true if find a Finish
21042  //entry before Fer or TimeLoc. No point checking for TimeTimeLoc since at a stop location now so a later TimeTimeLoc
21043  //must be preceded by a TimeLoc departure
21044  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsServiceTerminating");
21045  for(unsigned int x=1;x<TDEPtr->ActionVector.size();x++)
21046  {
21047  if((AVPtr + x) < TDEPtr->ActionVector.end())
21048  {
21049  AnsiString xx = (AVPtr + x)->Command;//test
21050  TTimetableFormatType xy = (AVPtr + x)->FormatType;//test
21051  TTimetableSequenceType xz = (AVPtr + x)->SequenceType;//test
21052  if(((AVPtr + x)->Command == "Fer") || ((AVPtr + x)->FormatType == TimeLoc))
21053  {
21054  Utilities->CallLogPop();
21055  return false;
21056  }
21057  else if((AVPtr + x)->SequenceType == FinishSequence)
21058  {
21059  Utilities->CallLogPop();
21060  return true;
21061  }
21062  }
21063  }
21064  Utilities->CallLogPop();
21065  return false;
21066  }
21067 */
21068 // ---------------------------------------------------------------------------
21069 
21070 void TTrainController::SendPerformanceSummary(int Caller, std::ofstream &PerfFile)
21071 {
21072  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SendPerformanceSummary");
21073  AnsiString FormatStr = "####0.0";
21074  AnsiString AvLateArrMins = "";
21075  AnsiString AvEarlyArrMins = "";
21076  AnsiString AvLatePassMins = "";
21077  AnsiString AvEarlyPassMins = "";
21078  AnsiString AvLateDepMins = "";
21079  AnsiString AvLateExitMins = "";
21080  AnsiString AvEarlyExitMins = "";
21081 
21082  //calculate remaining CumulativeDelayedRandMinsAllTrains for trains still in vector (CumulativeDelayedRandMinsAllTrains for exited or removed trains already accounted for)
21083  for(unsigned int x = 0; x < TrainVector.size(); x++)
21084  {
21085  Utilities->CumulativeDelayedRandMinsAllTrains += int(TrainVectorAt(89, x).CumulativeDelayedRandMinsOneTrain);
21086  }
21087 
21088  if(LateArrivals > 0)
21089  {
21090  AvLateArrMins = FormatFloat(FormatStr, (TotLateArrMins / LateArrivals));
21091  }
21092  if(EarlyArrivals > 0)
21093  {
21094  AvEarlyArrMins = FormatFloat(FormatStr, (TotEarlyArrMins / EarlyArrivals));
21095  }
21096  if(LatePasses > 0)
21097  {
21098  AvLatePassMins = FormatFloat(FormatStr, (TotLatePassMins / LatePasses));
21099  }
21100  if(EarlyPasses > 0)
21101  {
21102  AvEarlyPassMins = FormatFloat(FormatStr, (TotEarlyPassMins / EarlyPasses));
21103  }
21104  if(LateDeps > 0)
21105  {
21106  AvLateDepMins = FormatFloat(FormatStr, (TotLateDepMins / LateDeps));
21107  }
21108  if(LateExits > 0) //added at v2.9.1
21109  {
21110  AvLateExitMins = FormatFloat(FormatStr, (TotLateExitMins / LateExits));
21111  }
21112  if(EarlyExits > 0) //added at v2.9.1
21113  {
21114  AvEarlyExitMins = FormatFloat(FormatStr, (TotEarlyExitMins / EarlyExits));
21115  }
21116  PerfFile << '\n' << '\n' << "***************************************";
21117  PerfFile << '\n' << '\n' << "Performance summary:" << '\n';
21118 
21119  if(OnTimeArrivals != 1)
21120  {
21121  PerfFile << OnTimeArrivals << " on-time arrivals" << '\n';
21122  }
21123  else
21124  {
21125  PerfFile << OnTimeArrivals << " on-time arrival" << '\n';
21126  }
21127  if(LateArrivals > 1)
21128  {
21129  PerfFile << LateArrivals << " late arrivals (average " << AvLateArrMins.c_str() << " min)" << '\n';
21130  }
21131  else if(LateArrivals == 1)
21132  {
21133  PerfFile << LateArrivals << " late arrival (" << AvLateArrMins.c_str() << " min)" << '\n';
21134  }
21135  else
21136  {
21137  PerfFile << LateArrivals << " late arrivals" << '\n';
21138  }
21139  if(EarlyArrivals > 1)
21140  {
21141  PerfFile << EarlyArrivals << " early arrivals (average " << AvEarlyArrMins.c_str() << " min)" << '\n';
21142  }
21143  else if(EarlyArrivals == 1)
21144  {
21145  PerfFile << EarlyArrivals << " early arrival (" << AvEarlyArrMins.c_str() << " min)" << '\n';
21146  }
21147  else
21148  {
21149  PerfFile << EarlyArrivals << " early arrivals" << '\n';
21150  }
21151  if(OnTimePasses != 1)
21152  {
21153  PerfFile << OnTimePasses << " on-time passes" << '\n';
21154  }
21155  else
21156  {
21157  PerfFile << OnTimePasses << " on-time pass" << '\n';
21158  }
21159  if(LatePasses > 1)
21160  {
21161  PerfFile << LatePasses << " late passes (average " << AvLatePassMins.c_str() << " min)" << '\n';
21162  }
21163  else if(LatePasses == 1)
21164  {
21165  PerfFile << LatePasses << " late pass (" << AvLatePassMins.c_str() << " min)" << '\n';
21166  }
21167  else
21168  {
21169  PerfFile << LatePasses << " late passes" << '\n';
21170  }
21171  if(EarlyPasses > 1)
21172  {
21173  PerfFile << EarlyPasses << " early passes (average " << AvEarlyPassMins.c_str() << " min)" << '\n';
21174  }
21175  else if(EarlyPasses == 1)
21176  {
21177  PerfFile << EarlyPasses << " early pass (" << AvEarlyPassMins.c_str() << " min)" << '\n';
21178  }
21179  else
21180  {
21181  PerfFile << EarlyPasses << " early passes" << '\n';
21182  }
21183 
21184  if(OnTimeExits != 1) //this batch added at v2.9.1
21185  {
21186  PerfFile << OnTimeExits << " on-time exits" << '\n';
21187  }
21188  else
21189  {
21190  PerfFile << OnTimeExits << " on-time exit" << '\n';
21191  }
21192  if(LateExits > 1)
21193  {
21194  PerfFile << LateExits << " late exits (average " << AvLateExitMins.c_str() << " min)" << '\n';
21195  }
21196  else if(LateExits == 1)
21197  {
21198  PerfFile << LateExits << " late exit (" << AvLateExitMins.c_str() << " min)" << '\n';
21199  }
21200  else
21201  {
21202  PerfFile << LateExits << " late exits" << '\n';
21203  }
21204  if(EarlyExits > 1)
21205  {
21206  PerfFile << EarlyExits << " early exits (average " << AvEarlyExitMins.c_str() << " min)" << '\n';
21207  }
21208  else if(EarlyExits == 1)
21209  {
21210  PerfFile << EarlyExits << " early exit (" << AvEarlyExitMins.c_str() << " min)" << '\n';
21211  }
21212  else
21213  {
21214  PerfFile << EarlyExits << " early exits" << '\n';
21215  }
21216 
21217  if(OnTimeDeps != 1)
21218  {
21219  PerfFile << OnTimeDeps << " on-time departures" << '\n';
21220  }
21221  else
21222  {
21223  PerfFile << OnTimeDeps << " on-time departure" << '\n';
21224  }
21225  if(LateDeps > 1)
21226  {
21227  PerfFile << LateDeps << " late departures (average " << AvLateDepMins.c_str() << " min)" << '\n';
21228  }
21229  else if(LateDeps == 1)
21230  {
21231  PerfFile << LateDeps << " late departure (" << AvLateDepMins.c_str() << " min)" << '\n';
21232  }
21233  else
21234  {
21235  PerfFile << LateDeps << " late departures" << '\n';
21236  }
21237  TDateTime TempExcessLCDownTime;
21238  for(unsigned int x = 0; x < Track->BarriersDownVector.size(); x++) //added at v2.6.0 - should have been added earlier
21239  {
21240 // if(Track->BarriersDownVector.at(x).ReducedTimePenalty) //assume train still to cross LC as probably will, else have false high value & can have
21241  //later perf summaries with lower values, changed at v2.8.0
21242 // {
21243  TempExcessLCDownTime = TrainController->TTClockTime - Track->BarriersDownVector.at(x).StartTime - TDateTime(180.0 / 86400);
21244 // }
21245 /*
21246  else
21247  {
21248  TempExcessLCDownTime = TrainController->TTClockTime - Track->BarriersDownVector.at(x).StartTime;
21249  }
21250 */
21251  if(TempExcessLCDownTime > TDateTime(0))
21252  {
21253  TrainController->ExcessLCDownMins += (double(TempExcessLCDownTime) * 1440);
21254  }
21255  }
21256 
21257  AnsiString FormattedExcessLCDownMins = FormatFloat(FormatStr, ExcessLCDownMins);
21258 
21259  if(ExcessLCDownMins > 0.1)
21260  {
21261  PerfFile << FormattedExcessLCDownMins.c_str() << " excess minutes of level crossing barrier down time" << '\n';
21262  }
21263  else
21264  {
21265  ExcessLCDownMins = 0; //added at v2.16.1 so doesn't count towards performance score if < 0.1mins (else can have low score with no excess mins recorded)
21266  }
21267  if(Utilities->CumulativeDelayedRandMinsAllTrains > 0) //added at v2.13.0
21268  {
21269  PerfFile << Utilities->CumulativeDelayedRandMinsAllTrains << " minutes lost due to random delays when stopped at locations" << '\n';
21270  }
21271  if(MissedStops != 1)
21272  {
21273  PerfFile << MissedStops << " missed stops" << '\n';
21274  }
21275  else
21276  {
21277  PerfFile << MissedStops << " missed stop" << '\n';
21278  }
21279  if(OtherMissedEvents != 1)
21280  {
21281  PerfFile << OtherMissedEvents << " other missed events" << '\n';
21282  }
21283  else
21284  {
21285  PerfFile << OtherMissedEvents << " other missed event" << '\n';
21286  }
21287  if(SkippedTTEvents != 1)
21288  {
21289  PerfFile << SkippedTTEvents << " skipped timetable events" << '\n';
21290  }
21291  else
21292  {
21293  PerfFile << SkippedTTEvents << " skipped timetable event" << '\n';
21294  }
21295  if(UnexpectedExits != 1)
21296  {
21297  PerfFile << UnexpectedExits << " unexpected train exits" << '\n';
21298  }
21299  else
21300  {
21301  PerfFile << UnexpectedExits << " unexpected train exit" << '\n';
21302  }
21303  if(IncorrectExits != 1)
21304  {
21305  PerfFile << IncorrectExits << " incorrect train exits" << '\n';
21306  }
21307  else
21308  {
21309  PerfFile << IncorrectExits << " incorrect train exit" << '\n';
21310  }
21311  if(NumFailures != 1)
21312  {
21313  PerfFile << NumFailures << " train failures" << '\n';
21314  }
21315  else
21316  {
21317  PerfFile << NumFailures << " train failure" << '\n';
21318  }
21319  if(AvHoursIntValue > 0)
21320  {
21321  if(AvHoursIntValue == 1)
21322  {
21323  PerfFile << AvHoursIntValue << " hour mean time betweeen train failures" << '\n';
21324  }
21325  else
21326  {
21327  PerfFile << AvHoursIntValue << " hours mean time betweeen train failures" << '\n';
21328  }
21329  }
21330  AnsiString AvLateMinsLocsNotReached = "";
21331 
21333  int LocsNotReached = (NotStartedTrainLateArr + OperatingTrainLateArr); //dropped divide by 2 after 2.7.0 as don't count late departures as 'failed to arrive'
21334  // each location has an arrival and departure (generally) so divide by 2 - no, dropped after 2.7.0
21335 
21336  if(LocsNotReached > 0)
21337  {
21338  AvLateMinsLocsNotReached = FormatFloat(FormatStr, (OperatingTrainLateMins + NotStartedTrainLateMins) / (NotStartedTrainLateArr + OperatingTrainLateArr));
21339  PerfFile << LocsNotReached << " locations that trains failed to reach (average lateness " << AvLateMinsLocsNotReached.c_str() << " min)" << '\n';
21340  }
21341  if(SPADRisks != 1)
21342  {
21343  PerfFile << SPADRisks << " SPAD risks" << '\n';
21344  }
21345  else
21346  {
21347  PerfFile << SPADRisks << " SPAD risk" << '\n';
21348  }
21349  if(SPADEvents != 1)
21350  {
21351  PerfFile << SPADEvents << " SPADs" << '\n';
21352  }
21353  else
21354  {
21355  PerfFile << SPADEvents << " SPAD" << '\n';
21356  }
21357  if(Derailments != 1)
21358  {
21359  PerfFile << Derailments << " derailments" << '\n';
21360  }
21361  else
21362  {
21363  PerfFile << Derailments << " derailment" << '\n';
21364  }
21365  if(CrashedTrains != 1)
21366  {
21367  PerfFile << CrashedTrains << " crashed trains" << '\n';
21368  }
21369  else
21370  {
21371  PerfFile << CrashedTrains << " crashed train" << '\n';
21372  }
21373  PerfFile << '\n' << "***************************************" << '\n';
21374 
21375  bool DerailSPADFlag = false, CrashFlag = false;
21376 
21377  int OverallScorePercent = 100;
21378  int TotArrDepExit = 0;
21379  double TotLateMinsFactor = 1;
21380  double MissedStopAndSPADRiskFactor = 1;
21381  double NetNegFactor = 1;
21382 
21384  EarlyExits + LateExits + OnTimeExits; //exits added at v2.9.1, passes not counted
21385  // TotArrDep: total number of arrivals & departures including those for trains that haven't reached their destinations yet and are late
21386  // changed at v1.1.4 - calc was inside "if(OverallScorePercent == 100).." block so could remain 0 for SPADs & crashes, & then received the
21387  // 'no timetabled departures... message, which was inappropriate
21388 
21389  if((SPADEvents > 0) || (Derailments > 0))
21390  {
21391  OverallScorePercent = 5; // overrides other calculations
21392  DerailSPADFlag = true;
21393  }
21394  if(CrashedTrains > 0)
21395  {
21396  OverallScorePercent = 0; // overrides other calculations
21397  CrashFlag = true;
21398  }
21399  if(OverallScorePercent == 100)
21400  {
21401  int LatenessPenalty = TotLateArrMins + TotLateDepMins; //added at v2.13.0 for random delays
21402  if(Utilities->CumulativeDelayedRandMinsAllTrains > LatenessPenalty)
21403  {
21404  LatenessPenalty = 0;
21405  }
21406  else
21407  {
21408  LatenessPenalty -= Utilities->CumulativeDelayedRandMinsAllTrains;
21409  }
21410  if(TotArrDepExit > 0)
21411  {
21412  TotLateMinsFactor = exp((-0.1732) * (LatenessPenalty + OperatingTrainLateMins + NotStartedTrainLateMins + TotLateExitMins +
21413  ((OtherMissedEvents + SkippedTTEvents + UnexpectedExits + ExcessLCDownMins) * 15)) / TotArrDepExit); //exits added at v2.9.1
21414  // TotLateMinsFactor: negative exponential factor based on overall average arr & dep minutes late (with OtherMissedEvents & UnexpectedExits
21415  // counting as 15 mins late each), where 4 mins late average = half, 8 mins late = a quarter etc
21416  MissedStopAndSPADRiskFactor = exp((-17.33) * (MissedStops + SPADRisks + IncorrectExits) / TotArrDepExit);
21417  // MissedEventAndSPADRiskFactor: negative exponential factor based on number of missed stops, SPAD risks & IncorrectExits as a proportion
21418  // of arrivals & departures, where 4% = half, 8% = a quarter etc
21419  NetNegFactor = TotLateMinsFactor * MissedStopAndSPADRiskFactor;
21420  // NetNegfactor: product of the above two
21421  OverallScorePercent = 100 * NetNegFactor;
21422  }
21423  }
21424  if((TotArrDepExit > 0) || DerailSPADFlag || CrashFlag)
21425  // flag condits added at v1.1.4 - see above for what the error was
21426  {
21427  AnsiString OneFailureString = ", though the failure would account for some poor performance";
21428  AnsiString TwoOrMoreFailureString = ", though the failures would account for some poor performance";
21429  AnsiString AddedString = "";
21430  if(NumFailures == 1)
21431  {
21432  AddedString = OneFailureString;
21433  }
21434  if(NumFailures > 1)
21435  {
21436  AddedString = TwoOrMoreFailureString;
21437  }
21438  PerfFile << "\nOverall score: " << OverallScorePercent << "%\n";
21439  AnsiString Rating = "";
21440  if(OverallScorePercent == 100)
21441  {
21442  Rating = "Perfect!";
21443  }
21444  else if(OverallScorePercent >= 95)
21445  {
21446  Rating = "Excellent";
21447  }
21448  else if(OverallScorePercent >= 90)
21449  {
21450  Rating = "Very good";
21451  }
21452  else if(OverallScorePercent >= 80)
21453  {
21454  Rating = "Good";
21455  }
21456  else if(OverallScorePercent >= 70)
21457  {
21458  Rating = "Fair";
21459  }
21460  else if(OverallScorePercent >= 60)
21461  {
21462  Rating = "Unacceptable" + AddedString;
21463  }
21464  else if(OverallScorePercent >= 50)
21465  {
21466  Rating = "Poor" + AddedString;
21467  }
21468  else if(OverallScorePercent >= 40)
21469  {
21470  Rating = "Bad" + AddedString;
21471  }
21472  else if(OverallScorePercent >= 30)
21473  {
21474  Rating = "Very bad" + AddedString;
21475  }
21476  else if(OverallScorePercent >= 20)
21477  {
21478  Rating = "Terrible" + AddedString;
21479  }
21480  else if(OverallScorePercent >= 10)
21481  {
21482  Rating = "Appalling" + AddedString;
21483  }
21484  else if(OverallScorePercent >= 5)
21485  {
21486  if(DerailSPADFlag)
21487  {
21488  Rating = "Disastrous - potential loss of life";
21489  }
21490  // SPADs/Derailments
21491  else
21492  {
21493  Rating = "Dire" + AddedString;
21494  }
21495  }
21496  else if(OverallScorePercent < 5)
21497  {
21498  if(CrashFlag)
21499  {
21500  Rating = "Catastrophic - loss of life"; // Crashes
21501  }
21502  else
21503  {
21504  Rating = "Abysmal";
21505  }
21506  }
21507  PerfFile << "Overall rating: " << Rating.c_str() << '\n';
21508  }
21509  else
21510  {
21511  PerfFile << "\nThere were no timetabled departures, arrivals or exits so there is insufficient information to provide a performance score or rating" << '\n';
21512  }
21513  PerfFile << '\n' << "***************************************";
21514  PerfFile.flush();
21515  Utilities->CallLogPop(1736);
21516 }
21517 
21518 // ---------------------------------------------------------------------------
21519 
21521 {
21522  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SetWarningFlags");
21523  for(unsigned int x = 0; x < TrainVector.size(); x++)
21524  {
21525  TTrain &Train = TrainVectorAt(58, x);
21526  if(Train.Crashed)
21527  // can't use background colours for crashed & derailed because same colour
21528  {
21529  CrashWarning = true;
21530  }
21531  else if(Train.Derailed)
21532  // can't use background colours for crashed & derailed because same colour
21533  {
21534  DerailWarning = true;
21535  }
21536  else if(Train.BackgroundColour == clSPADBackground)
21537  // use colour as that changes as soon as passes signal
21538  {
21539  SPADWarning = true;
21540  }
21541  else if(Train.BackgroundColour == clTrainFailedBackground)
21542  {
21543  TrainFailedWarning = true;
21544  }
21545  else if(Train.BackgroundColour == clCallOnBackground)
21546  // use colour as also stopped at signal
21547  {
21548  CallOnWarning = true;
21549  }
21550  else if(Train.BackgroundColour == clSignalStopBackground)
21551  // use colour to distinguish from call-on
21552  {
21553  SignalStopWarning = true;
21554  }
21555  else if(Train.BackgroundColour == clBufferAttentionNeeded)
21556  // use colour to distinguish from ordinary buffer stop
21557  {
21558  BufferAttentionWarning = true;
21559  }
21560  }
21561  Utilities->CallLogPop(1796);
21562 }
21563 
21564 // ---------------------------------------------------------------------------
21565 
21567 {
21568  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CalcSignalStopLateness");
21569 
21570  // calculate lateness for running trains
21573  for(unsigned int x = 0; x < TrainVector.size(); x++)
21574  {
21575  TTrain &Train = TrainVectorAt(64, x);
21576  for(TActionVectorEntry * AVEntryPtr = &Train.TrainDataEntryPtr->ActionVector.front(); AVEntryPtr < &Train.TrainDataEntryPtr->ActionVector.back();
21577  AVEntryPtr++)
21578  {
21579  if(AVEntryPtr < Train.ActionVectorEntryPtr)
21580  {
21581  continue;
21582  }
21583  if((AVEntryPtr->ArrivalTime > TDateTime(-1)) && !Train.RevisedStoppedAtLoc() && (GetRepeatTime(42, AVEntryPtr->ArrivalTime, Train.RepeatNumber, Train.IncrementalMinutes) <
21584  TTClockTime))
21585  {
21586  OperatingTrainLateMins += 1440 * double(TTClockTime - GetRepeatTime(43, AVEntryPtr->ArrivalTime, Train.RepeatNumber, Train.IncrementalMinutes));
21588  }
21589 /* dropped departures after 2.7.0 because these don't count for 'failed to reach' numbers
21590  if((AVEntryPtr->DepartureTime > TDateTime(-1)) && (GetRepeatTime(44, AVEntryPtr->DepartureTime, Train.RepeatNumber, Train.IncrementalMinutes) <
21591  TTClockTime))
21592  {
21593  OperatingTrainLateMins += 1440 * double(TTClockTime - GetRepeatTime(45, AVEntryPtr->DepartureTime, Train.RepeatNumber, Train.IncrementalMinutes));
21594  OperatingTrainArrDep++;
21595  }
21596 */
21597  }
21598  }
21599 
21600  // calculate lateness for trains that haven't started yet (could be held awaiting entry)
21603 
21604  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
21605  {
21606  TTrainDataEntry & TDEntry = TrainDataVector.at(x);
21607  const TActionVectorEntry &AVEntryLast = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
21608  int IncrementalMinutes = 0;
21609  if(AVEntryLast.FormatType == Repeat)
21610  {
21611  IncrementalMinutes = AVEntryLast.RearStartOrRepeatMins;
21612  }
21613  for(int y = 0; y < TDEntry.NumberOfTrains; y++)
21614  {
21615  TTrainOperatingData &TTOD = TDEntry.TrainOperatingDataVector.at(y);
21616  if(TTOD.RunningEntry != NotStarted)
21617  {
21618  continue;
21619  }
21620  // note that can't rely on the above for sessionfiles saved before v0.6b as wasn't set to Running for Sns/Fsp/rsp & shuttles
21621  // but if trains had exited then would be set to Exited, so need to check against trains still operating - use the test below
21622  bool TrainOperatingFlag = false;
21623  for(unsigned int a = 0; a < TrainController->TrainVector.size(); a++)
21624  {
21625  if((TrainController->TrainVector.at(a).TrainDataEntryPtr == &TDEntry) && (TrainController->TrainVector.at(a).RepeatNumber == y))
21626  {
21627  TrainOperatingFlag = true;
21628  break;
21629  }
21630  }
21631  if(TrainOperatingFlag)
21632  {
21633  continue;
21634  }
21635  if(GetRepeatTime(46, TDEntry.ActionVector.at(0).EventTime, y, IncrementalMinutes) > TTClockTime)
21636  {
21637  break; // if the first time is greater than TTClockTime then all the rest will also be greater (& default of -1 will be less so will be ignored)
21638  }
21639  for(unsigned int z = 0; z < TDEntry.ActionVector.size(); z++)
21640  {
21641  TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(z);
21642  if(GetRepeatTime(35, AVEntry.EventTime, y, IncrementalMinutes) > TTClockTime)
21643  {
21644  break; // all the rest will also be greater (& default of -1 will be less)
21645  }
21646  if(GetRepeatTime(36, AVEntry.ArrivalTime, y, IncrementalMinutes) > TTClockTime)
21647  {
21648  break; // all the rest will also be greater (& default of -1 will be less)
21649  }
21650  if(GetRepeatTime(37, AVEntry.DepartureTime, y, IncrementalMinutes) > TTClockTime)
21651  {
21652  break; // all the rest will also be greater (& default of -1 will be less)
21653  }
21654  if((AVEntry.ArrivalTime > TDateTime(-1)) && (GetRepeatTime(38, AVEntry.ArrivalTime, y, IncrementalMinutes) < TTClockTime))
21655  {
21656  NotStartedTrainLateMins += 1440 * double(TTClockTime - GetRepeatTime(39, AVEntry.ArrivalTime, y, IncrementalMinutes));
21658  }
21659 /* dropped departures after 2.7.0 as only interested in 'failed to reach' number - if train hasn't arrived then it hasn't departed so shouldn't count that as part of 'failed to reach'
21660  if((AVEntry.DepartureTime > TDateTime(-1)) && (GetRepeatTime(40, AVEntry.DepartureTime, y, IncrementalMinutes) < TTClockTime))
21661  {
21662  NotStartedTrainLateMins += 1440 * double(TTClockTime - GetRepeatTime(41, AVEntry.DepartureTime, y, IncrementalMinutes));
21663  NotStartedTrainArrDep++;
21664  }
21665 */
21666  }
21667  }
21668  }
21669  Utilities->CallLogPop(1894);
21670 }
21671 
21672 // ---------------------------------------------------------------------------
21673 
21675 // new v2.2.0 for OperatorActionPanel (OperatorActionPanel changed for ActionsDueForm at v2.13.0)
21676 // clears entries then adds values for running trains then for continuation entries
21677 // dont limit size here as need to check all trains (ActionsDueListBox is limited to 20 trains in Interface.cpp)
21678 {
21679  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RebuildOpTimeToActMultimap");
21680  OpTimeToActMultiMap.clear();
21681  TOpTimeToActMultiMapEntry OpTimeToActMultiMapEntry;
21682 
21683  if(!TrainVector.empty())
21684  // build OpTimeToActMultiMap entries for running trains
21685  {
21686  AnsiString HeadCode;
21687  // dropped in favour of TrainID for running trains int VecPos; //TrackVectorPosition of LeadElement or continuation where train is to enter
21688  int TrainID;
21689  THCandTrainPosParam HCandTrainPosParam;
21690  for(unsigned int x = 0; x < TrainVector.size(); x++)
21691  {
21692  HeadCode = TrainVectorAt(62, x).HeadCode;
21693  TrainID = TrainVectorAt(63, x).TrainID;
21694  HCandTrainPosParam.first = HeadCode;
21695  HCandTrainPosParam.second = TrainID;
21696  float TimeToAct = TrainVectorAt(65, x).OpTimeToAct;
21697  if((TimeToAct >= 0) && (TimeToAct < 59.9))
21698  // -1 indicates don't display
21699  {
21700  OpTimeToActMultiMapEntry.first = TimeToAct;
21701  OpTimeToActMultiMapEntry.second = HCandTrainPosParam;
21702  OpTimeToActMultiMap.insert(OpTimeToActMultiMapEntry);
21703  }
21704  }
21705  }
21706 /*
21707  * class TContinuationTrainExpectationEntry
21708  {
21709  public:
21710  AnsiString Description; ///< service description
21711  AnsiString HeadCode; ///< service headcode
21712  int RepeatNumber; ///< service RepeatNumber
21713  int IncrementalMinutes; ///< Repeat separation in minutes
21714  int IncrementalDigits; ///< Repeat headcode separation
21715  int VectorPosition; ///< TrackVectorPosition for the continuation element
21716  TTrainDataEntry *TrainDataEntryPtr; ///< points to the service entry in the timetable's TrainDataVector
21717  };
21718 
21719  Multimap class for TContinuationTrainExpectationEntry objects, where the access key is the expectation time
21720  typedef std::multimap<TDateTime, TContinuationTrainExpectationEntry> TContinuationTrainExpectationMultiMap;
21721  typedef TContinuationTrainExpectationMultiMap::iterator TContinuationTrainExpectationMultiMapIterator; ///< iterator for the multimap
21722  typedef std::pair<TDateTime, TContinuationTrainExpectationEntry> TContinuationTrainExpectationMultiMapPair; ///< a single multimap entry
21723 */
21724 
21726  // build OpTimeToActMultiMap entries for expected trains
21727  {
21728  // note that using the ContinuationTrainExpectationMultiMap automatically ensures that entries are in ascending time order
21729  // first have to calculate times to red signal for each train due to enter (ignore later trains as will likely change before they are due)
21730  float TimeToAct = 0; // minutes
21731  int DistanceToRedSignal = 0; // metres
21732  TContinuationEntryVecPosVector ContinuationEntryVecPosVector;
21733  // used to ensure only one train displayed for a given continuation
21734  ContinuationEntryVecPosVector.clear();
21735  bool LaterTrain = false;
21738  {
21739  LaterTrain = false;
21740  if(CTEIt->second.TrainDataEntryPtr->TrainOperatingDataVector.at(CTEIt->second.RepeatNumber).RunningEntry != NotStarted)
21741  {
21742  CTEIt++;
21743  continue; // not interested in running or exited trains
21744  }
21745  if(Track->TrackElementAt(934, CTEIt->second.VectorPosition).TrainIDOnElement > 0)
21746  {
21747  CTEIt++;
21748  continue;
21749  // don't include trains not entered yet when a train is already on the continuation
21750  }
21751  if(!ContinuationEntryVecPosVector.empty())
21752  {
21753  for(unsigned int x = 0; x < ContinuationEntryVecPosVector.size(); x++)
21754  {
21755  if(CTEIt->second.VectorPosition == ContinuationEntryVecPosVector.at(x))
21756  {
21757  LaterTrain = true;
21758  ;
21759  // skip past remaining trains waiting to enter at same point
21760  break;
21761  }
21762  }
21763  }
21764  if(LaterTrain)
21765  {
21766  CTEIt++;
21767  continue;
21768  }
21769  ContinuationEntryVecPosVector.push_back(CTEIt->second.VectorPosition);
21770  AnsiString HeadCode = CTEIt->second.HeadCode;
21771  float CurrentStopTime; // set to 0 at start of function
21772  float LaterStopTime; // set to 0 at start of function
21773  float RecoverableTime; // set to 0 at start of function
21774  int AvTrackSpeed; // set to 0 at start of function
21775  int TrainID = -1; // not yet allocated for train still to enter
21776  int DistanceToExit; //not used for continuation entries
21777  THVShortPair ExitPair;
21778  bool SigControlAndCanPassRedSignal = false; // doesn't apply for a continuation
21779 
21780 //at v2.11.0 found that with *AVPtr set to ...ActionVector.at(0) below instead of ...at(1) to stop signaller control trains throwing an error (because there is no ...at(1) -
21781 //discovered with Birmingham) the LaterStopTime isn't calculated and if a train does something other than depart after an arrival it is still listed in the actions due panel -
21782 //because it just calcs the distance to the red signal and converts that to a time. So here (after v2.11.0) this new test is introduced to determine whether a train is a
21783 //signaller control train (when the ActionVector size is 1) or not (ActionVector size > 1), and the ...at(value) is set accordingly - 0 for signaller control or 1 if not.
21784 
21785  int AtValue = 1;
21786  if(CTEIt->second.TrainDataEntryPtr->ActionVector.size() == 1)
21787  {
21788  AtValue = 0;
21789  }
21790  DistanceToRedSignal = CalcDistanceToRedSignalandStopTime(1, CTEIt->second.VectorPosition, 0,
21791  // EntryPos always 0 for entering at a continuation
21792  SigControlAndCanPassRedSignal, &CTEIt->second.TrainDataEntryPtr->ActionVector.at(AtValue), //see above note
21793  HeadCode, TrainID, CurrentStopTime, LaterStopTime, RecoverableTime, AvTrackSpeed, DistanceToExit, ExitPair);
21794  // for above VectorPosition is the first element to have its length included in the sum, so for a continuation it's the continuation itself
21795  // for a train it's the one in front of LeadElement
21796  if(AvTrackSpeed < 30)
21797  {
21798  AvTrackSpeed = 30;
21799  }
21800  if(DistanceToRedSignal == -1)
21801  {
21802  TimeToAct = 60.0;
21803  }
21804  else
21805  {
21806  int Speed = AvTrackSpeed;
21807  int MaxSpeed = int(CTEIt->second.TrainDataEntryPtr->MaxRunningSpeed);
21808  if(AvTrackSpeed > MaxSpeed)
21809  {
21810  Speed = MaxSpeed;
21811  }
21812  if(CTEIt->second.TrainDataEntryPtr->ActionVector.at(0).SignallerControl) //changed to ...at(0) from at(1) at v2.11.0 as SignallerControl only valid for ..at(0)
21813  // defined in timetable as under signaller control
21814  {
21815  Speed = CTEIt->second.TrainDataEntryPtr->SignallerSpeed;
21816  LaterStopTime = 0;
21817  }
21818  TimeToAct = LaterStopTime + DistanceToRedSignal * 3.6 / 60 / Speed;
21819  // accel & decel taken into account in
21820  // CalcDistanceToRedSignalandStopTime
21821  // 3.6 convertsKm/h to m/s & 60 converts seconds to minutes
21822  // don't need CurrentStopTime or RecoverableTime for continuation entries
21823  float MinsBefEnter = double(CTEIt->first - TTClockTime) * 86400.0 / 60.0;
21824  TimeToAct += MinsBefEnter;
21825  }
21826  THCandTrainPosParam HCandTrainPosParam;
21827  HCandTrainPosParam.first = HeadCode;
21828  HCandTrainPosParam.second = -1 - CTEIt->second.VectorPosition;
21829  // -1-CTE... because 2nd value covers TrainID if +ve &
21830  // continuation track vector position if -ve, -1 allows for vecpos being 0
21831  if(TimeToAct < 59.9) // if 60 don't enter a value in multimap
21832  {
21833  OpTimeToActMultiMapEntry.first = TimeToAct;
21834  OpTimeToActMultiMapEntry.second = HCandTrainPosParam;
21835  OpTimeToActMultiMap.insert(OpTimeToActMultiMapEntry);
21836  }
21837  CTEIt++;
21838  }
21839  }
21840  Utilities->CallLogPop(2081);
21841 }
21842 
21843 // ---------------------------------------------------------------------------
21844 
21846 // new for multiplayer
21847 // clears entries then adds values for running trains
21848 {
21849  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RebuildTimeToExitMultiMap");
21850  TimeToExitMultiMap.clear();
21851  TTimeToExitMultiMapEntry TimeToExitMultiMapEntry;
21852 
21853  if(!TrainVector.empty())
21854  // build map entries for running trains
21855  {
21856  TExitInfo ExitInfo; //corresponds to TServiceInfo in Interface
21857  THVShortPair ExitPair;
21858  float TimeToExit;
21859  for(unsigned int x = 0; x < TrainVector.size(); x++)
21860  {
21862  ExitInfo.RepeatNumber = short(TrainVectorAt(81, x).RepeatNumber);
21863  ExitInfo.TimeToExitSecs = short(TrainVectorAt(77, x).TimeToExit * 60);
21864  ExitPair = TrainVectorAt(76, x).ExitPair;
21865  if((ExitInfo.TimeToExitSecs >= 3570) || (ExitInfo.TimeToExitSecs < 1)) //59.5 mins or -60 secs
21866  {
21867  ExitInfo.TimeToExitSecs = -1;
21868  }
21869  TimeToExitMultiMapEntry.first = ExitPair;
21870  TimeToExitMultiMapEntry.second = ExitInfo;
21871  TimeToExitMultiMap.insert(TimeToExitMultiMapEntry);
21872  }
21873  }
21874  Utilities->CallLogPop(2323);
21875 }
21876 
21877 // ---------------------------------------------------------------------------
21878 
21879 int TTrainController::CalcDistanceToRedSignalandStopTime(int Caller, int TrackVectorPosition, int TrackVectorPositionEntryPos,
21880  bool SigControlAndCanPassRedSignal, TActionVectorEntry *AVPtr, AnsiString HeadCode, int TrainID, float &CurrentStopTime, float &LaterStopTime,
21881  float &RecoverableTime, int &AvTrackSpeed, int &DistanceToExit, THVShortPair &ExitPair)
21882 // new v2.2.0
21883 // vectorPosition is the value for the first element to be measured - for a continuation it's the continuation itself, for a train
21884 // it's the one after LeadElement, returns -1 for infinity - i.e. not approaching red signal (e.g. maybe cdt before reach it).
21885 // CurrentStopTime is the time to depart from the current station (if stopped at a station) and LaterStopTime is the total station
21886 // stop times for stations after the current one. DistanceToRedSignal is what the name implies, if -1 is returned the other values
21887 // aren't used - this means there is no display for the train in question
21888 {
21889  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",DistanceToRedSignal, " + AnsiString(TrackVectorPosition) + ", " +
21890  AnsiString(TrackVectorPositionEntryPos) + ", " + AVPtr->Command);
21891  int DistanceToRedSignal = 0;
21892  DistanceToExit = -1;
21893  ExitPair.first = -1;
21894  ExitPair.second = -1;
21895  int CumTrackSpeed = 0;
21896  // average track speed, in case need to use in time calc
21897  int TrackSpeedCount = 0;
21898  float KmPerLocationStop;
21899  float MaxAllowableSpeed;
21900 
21901  //below added at v2.6.1
21902  if(TrainID > -1) //will be -1 for trains not entered yet
21903  {
21904  TTrain &Train = TrainVectorAtIdent(51, TrainID);
21905  Train.DistanceToStationStop = 0; //if find a red signal first then this distance isn't needed
21906  Train.StationStopCalculated = false;
21907  }
21908  AvTrackSpeed = 0;
21909  int CurrentElement = TrackVectorPosition;
21910  int CurrentEntryPos = TrackVectorPositionEntryPos;
21911  int NextElement;
21912  int NextEntryPos;
21913  int NextExitPos;
21914 
21915  CurrentStopTime = 0;
21916  LaterStopTime = 0;
21917  RecoverableTime = 0;
21918  if(CurrentElement == -1) // train on end element, no action needed
21919  {
21920  Utilities->CallLogPop(2094);
21921  return(-1);
21922  }
21923  int CurrentExitPos;
21924 
21925  // get ExitPos for first element to be measured
21926  if(Track->TrackElementAt(935, CurrentElement).TrackType == Points)
21927  {
21928  if((CurrentEntryPos == 0) || (CurrentEntryPos == 2)) // leading point
21929  {
21930  if(Track->TrackElementAt(936, CurrentElement).Attribute == 0)
21931  {
21932  CurrentExitPos = 1;
21933  }
21934  else
21935  {
21936  CurrentExitPos = 3;
21937  }
21938  }
21939  else
21940  {
21941  CurrentExitPos = 0; // trailing point
21942  }
21943  }
21944  else
21945  {
21946  CurrentExitPos = Track->GetNonPointsOppositeLinkPos(CurrentEntryPos);
21947  }
21948  // get CumTrackSpeed for first measured element
21949 
21950  TConfiguration CurrentExitConfig = Track->TrackElementAt(937, CurrentElement).Config[CurrentExitPos];
21951  int CurrentAttribute = Track->TrackElementAt(938, CurrentElement).Attribute;
21952  bool CurrentElementFailed = Track->TrackElementAt(1549, CurrentElement).Failed; //added at v2.13.2
21953 
21954  // check if currently stopped at a location, and if so add the remaining dwell time
21955  // can't use CurrentElement as that is in front of LeadElement and might not be at the location
21956  if(TrainID > -1)
21957  // -1 for a continuation and can't be at a location as not yet entered
21958  {
21959  TTrain &Train = TrainVectorAtIdent(39, TrainID); //Train wasn't a reference before v2.6.1 mods so FirstLaterStopRecoverableTime wouldn't be reset for the referenced train
21961  // this used to deduct from RecoverableTime when arrive at a location
21962  if(Train.RevisedStoppedAtLoc())
21963  {
21964  if(Train.StoppedForTrainInFront || Train.TrainInFront)
21965  {
21966  Utilities->CallLogPop(2082);
21967  return(-1); // no action needed
21968  }
21970  { //added '|| (Train.ActionVectorEntryPtr->FormatType == TimeCmdDescription)' at v2.16.1 so description ignored in calculating action due time
21971  Utilities->CallLogPop(2083);
21972  return(-1); // not due a departure or a description change so no action needed
21973  }
21974  else if(((Train.ActionVectorEntryPtr + 1)->FormatType == TimeLoc) && (Train.ActionVectorEntryPtr->FormatType == TimeCmdDescription)) // due a departure immediately after change of description
21975  { //added at v2.16.1 to cover description change due next then a departure
21976  double TimeToDepart = double((Train.GetTrainTime(68, (Train.ActionVectorEntryPtr + 1)->DepartureTime)) - TrainController->TTClockTime) * 86400 / 60; // mins to depart excluding possible 30sec allowance from LastActionTime
21977  //need repeat time for the above
21978  if((Train.ActionVectorEntryPtr + 1)->DepartureTime == Train.ActionVectorEntryPtr->EventTime) //don't need repeat time here
21979  {
21980  TimeToDepart+= 0.5; //add in the 30 secs if depature time same as description change time
21981  }
21982  if(TimeToDepart < 0.5)
21983  {
21984  TimeToDepart = 0.5;
21985  }
21986  // can't convert a TDateTime to a float directly
21987  CurrentStopTime = float(TimeToDepart);
21988  AVPtr++;
21989  AVPtr++;
21990  }
21991  else if((Train.ActionVectorEntryPtr->FormatType == TimeLoc) || (Train.ActionVectorEntryPtr->FormatType == TimeTimeLoc)) // due a departure as next action
21992  {
21993  double TimeToDepart = double(Train.ReleaseTime - TrainController->TTClockTime) * 86400 / 60; // mins to depart
21994  // can't convert a TDateTime to a float directly
21995  CurrentStopTime = float(TimeToDepart);
21996  AVPtr++;
21997  }
21998  else //added at v2.16.1 to catch all other combinations
21999  { //none of the above so no action needed
22000  Utilities->CallLogPop(2628);
22001  return(-1);
22002  }
22003  }
22004  }
22005  // check if CurrentElement is a red signal, but ok if autosig route after provided signal not failed
22006  if((CurrentExitConfig == Signal) && (CurrentAttribute == 0))
22007  // ok if autosig route after red signal unless signal has failed
22008  {
22009  int NextElement = Track->TrackElementAt(939, CurrentElement).Conn[CurrentExitPos];
22010  int NextEntryPos = Track->TrackElementAt(940, CurrentElement).ConnLinkPos[CurrentExitPos];
22011  int RouteNumber; // holder for referenced value, not used
22012  if((AllRoutes->GetRouteTypeAndNumber(33, NextElement, NextEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute) && !CurrentElementFailed)
22013  { //CurrentElementFailed added at v2.13.2
22014  Utilities->CallLogPop(2078);
22015  return(-1);
22016  }
22017  else if(SigControlAndCanPassRedSignal)
22018  // ignore signal and increment CurrentElement to NextElement
22019  {
22020  if(Track->TrackElementAt(941, NextElement).TrackType == Points)
22021  {
22022  if((NextEntryPos == 0) || (NextEntryPos == 2))
22023  // leading entry point
22024  {
22025  if(Track->TrackElementAt(942, NextElement).Attribute == 0)
22026  {
22027  NextExitPos = 1;
22028  }
22029  else
22030  {
22031  NextExitPos = 3;
22032  }
22033  }
22034  else
22035  {
22036  NextExitPos = 0; // trailing entry point
22037  }
22038  }
22039  else
22040  {
22041  NextExitPos = Track->GetNonPointsOppositeLinkPos(NextEntryPos);
22042  }
22043  CurrentElement = NextElement;
22044  CurrentEntryPos = NextEntryPos;
22045  CurrentExitPos = NextExitPos;
22046  CurrentExitConfig = Track->TrackElementAt(943, CurrentElement).Config[CurrentExitPos];
22047  CurrentAttribute = Track->TrackElementAt(944, CurrentElement).Attribute;
22048  }
22049  else if((TrainID > -1) && (TrainVectorAtIdent(40, TrainID).TrainMode == Timetable)) // ignore signallercontrol or will
22050  // give 'NOW' indication after allowed to pass stop signal when LeadMidLag (AllowedToPassRedSignal reset by this point)
22051  {
22052  Utilities->CallLogPop(2084);
22053  return(0);
22054  // stopped with red signal in front, don't need AvSpeedLimit in this case, & if at location awaiting departure dwell time already calculated
22055  }
22056  }
22057  int LaterStopNumber = 0;
22058  int x = 0;
22059  // added in v2.4.0 to prevent endless circling round track loops - spotted by Xeon 09/03/20 & reported by emsil
22060 
22061  while(!((CurrentExitConfig == Signal) && (CurrentAttribute == 0)))
22062  // not red signal next (in fwd direction) so enter loop to calc CumLength
22063  {
22064  x++; // added in v2.4.0 as above
22065  if(x > 5000)
22066  {
22067  Utilities->CallLogPop(2120);
22068  return(-1);
22069  }
22070  if(CurrentEntryPos > 1)
22071  {
22072  DistanceToRedSignal += Track->TrackElementAt(916, CurrentElement).Length23;
22073  CumTrackSpeed += Track->TrackElementAt(945, CurrentElement).SpeedLimit23;
22074  }
22075  else
22076  {
22077  DistanceToRedSignal += Track->TrackElementAt(917, CurrentElement).Length01;
22078  CumTrackSpeed += Track->TrackElementAt(946, CurrentElement).SpeedLimit01;
22079  }
22080  TrackSpeedCount++;
22081 
22082  //added for multiplayer - exiting at a continuation and continuation length already added
22083  if((Track->TrackElementAt(1407, CurrentElement).TrackType == Continuation) && (Track->TrackElementAt(1408, CurrentElement).Config[CurrentExitPos] == End))
22084  {
22085  DistanceToExit = DistanceToRedSignal; //don't need to exit function here as will exit when find that the next Conn value is -1
22086  ExitPair.first = Track->TrackElementAt(1409, CurrentElement).HLoc;
22087  ExitPair.second = Track->TrackElementAt(1410, CurrentElement).VLoc;
22088  //here repeat calcs for MaxAllowableSpeed & AvTrackSpeed as done at end for stop signal
22089  //need here as next element will be -1 so will exit before calcs at end
22090  if(TrackSpeedCount > 0)
22091  {
22092  MaxAllowableSpeed = CumTrackSpeed / TrackSpeedCount;
22093  }
22094  else // shouldn't reach here but include to prevent divide by zero error
22095  {
22096  if(CurrentEntryPos > 1)
22097  {
22098  MaxAllowableSpeed = Track->TrackElementAt(951, CurrentElement).SpeedLimit23;
22099  }
22100  else
22101  {
22102  MaxAllowableSpeed = Track->TrackElementAt(952, CurrentElement).SpeedLimit01;
22103  }
22104  // Train MaxRunningSpeed taken into account in RebuildOpTimeToActMultimap
22105  }
22106  //calc AvTrackSpeed
22107  if(LaterStopNumber > 0)
22108  {
22109  KmPerLocationStop = float(DistanceToRedSignal) / LaterStopNumber / 1000; // m to km
22110  AvTrackSpeed = (8.75 * KmPerLocationStop) + 44;
22111  // Av speed calculation based on formula: Speed = 8.75*(kms/location stop) + 44 km/sec (from experiments), subject to a maximum of
22112  // average line speed/2 (for half distance accelerating and half decelerating.
22113  }
22114  else
22115  {
22116  AvTrackSpeed = (sqrt(float(DistanceToRedSignal) / 1000) * 44) + 60;
22117  // using linear trendline for accel & decel distance at various speeds
22118  // at half braking, speed never < 60 using this
22119  }
22120  if(AvTrackSpeed > MaxAllowableSpeed)
22121  {
22122  AvTrackSpeed = MaxAllowableSpeed;
22123  }
22124  }
22125 
22126  // added at v2.6.1 to find DistanceToStationStop for trains running early
22127  if(TrainID > -1) //can ignore continuation entries as these don't run early
22128  {
22129  TTrain &Train = TrainVectorAtIdent(52, TrainID);
22130  if(!Train.StationStopCalculated)
22131  {
22132  if(Train.TrainMode == Timetable)
22133  {
22134  bool StopRequired = false;
22135  if(!Train.TimetableFinished && (Train.NameInTimetableBeforeCDT(16, Track->TrackElementAt(1005, CurrentElement).ActiveTrackElementName,
22136  StopRequired) > -1) && ((Track->TrackElementAt(1006, CurrentElement).StationEntryStopLinkPos1 == CurrentEntryPos) ||
22137  (Track->TrackElementAt(1010, CurrentElement).StationEntryStopLinkPos2 == CurrentEntryPos) ||
22138  (Track->TrackElementAt(1654, CurrentElement).StationEntryStopLinkPos3 == CurrentEntryPos) ||
22139  (Track->TrackElementAt(1655, CurrentElement).StationEntryStopLinkPos4 == CurrentEntryPos)))
22140  {
22141  // no need to add in the length of element to CumulativeLength
22142  if(StopRequired)
22143  {
22144  Train.DistanceToStationStop = DistanceToRedSignal; // DistanceToRedSignal holds the intermediate distance to this point
22145  Train.StationStopCalculated = true; //don't want to update it with later stops
22146  }
22147  }
22148  }
22149  }
22150  }
22151  // check for train in front, but if on a bridge on other track then ok
22152  TTrackElement TE = Track->TrackElementAt(947, CurrentElement);
22153  int TrainOnElement;
22154  if(TE.TrackType != Bridge)
22155  {
22156  TrainOnElement = TE.TrainIDOnElement;
22157  }
22158  else
22159  {
22160  if(CurrentEntryPos > 1)
22161  {
22163  }
22164  else
22165  {
22167  }
22168  }
22169  if((TrainOnElement > -1) && (TrainOnElement != TrainID))
22170  // train in front before red signal
22171  {
22172  Utilities->CallLogPop(2085);
22173  return(-1);
22174  }
22175  // add to stoptime if required
22176  if(Track->TrackElementAt(948, CurrentElement).ActiveTrackElementName != "")
22177  {
22178  double StopTimeDouble;
22179  while(AVPtr->FormatType == PassTime)
22180  {
22181  AVPtr++; // skip past any passes
22182  }
22183  if((Track->TrackElementAt(949, CurrentElement).ActiveTrackElementName == AVPtr->LocationName) && ((AVPtr->FormatType == TimeLoc) ||
22184  (AVPtr->FormatType == TimeTimeLoc)))
22185  // stop due here so calc dwell time & advance Ptr
22186  {
22187  if(AVPtr->FormatType == TimeTimeLoc)
22188  {
22189  LaterStopNumber++;
22190  StopTimeDouble = double(AVPtr->DepartureTime - AVPtr->ArrivalTime) * 86400.0 / 60.0;
22191  if(StopTimeDouble < 0.5)
22192  {
22193  StopTimeDouble = 0.5;
22194  }
22195  // at least 30 secs delay at station
22196  // can't convert a TDateTime to a float directly
22197  LaterStopTime += float(StopTimeDouble);
22198  RecoverableTime += StopTimeDouble - 0.5;
22199  if((LaterStopNumber == 1) && (TrainID > -1))
22200  {
22201  TrainVectorAtIdent(41, TrainID).FirstLaterStopRecoverableTime = RecoverableTime;
22202  }
22203  AVPtr++;
22204  }
22205  else if((AVPtr->FormatType == TimeLoc) && (AVPtr->ArrivalTime != TDateTime(-1))) // must be an arrival
22206  {
22207  if((AVPtr + 1)->FormatType == TimeLoc)
22208  // must be a departure
22209  {
22210  LaterStopNumber++;
22211  StopTimeDouble = double((AVPtr + 1)->DepartureTime - AVPtr->ArrivalTime) * 86400.0 / 60.0; //diff will be same for all repeats
22212  // can't convert a TDateTime to a float directly //so repeat times not required
22213  if(TrainID > -1) //exclude trains still to enter
22214  {
22215  TTrain &Train = TrainVectorAtIdent(67, TrainID);
22216  if(TTClockTime > Train.GetTrainTime(69, AVPtr->ArrivalTime)) //running late, added at v2.16.1
22217  {
22218  StopTimeDouble = double(Train.GetTrainTime(70, (AVPtr + 1)->DepartureTime) - TTClockTime) * 86400.0 / 60.0; //may be -ve but if so it's set to 0.5 later
22219  // can't convert a TDateTime to a float directly
22220  }
22221  }
22222  if(StopTimeDouble < 0.5)
22223  {
22224  StopTimeDouble = 0.5;
22225  }
22226  // at least 30 secs delay at station
22227  LaterStopTime += float(StopTimeDouble);
22228  RecoverableTime += StopTimeDouble - 0.5;
22229  if((LaterStopNumber == 1) && (TrainID > -1))
22230  {
22231  TrainVectorAtIdent(42, TrainID).FirstLaterStopRecoverableTime = RecoverableTime;
22232  }
22233  AVPtr++;
22234  AVPtr++;
22235  }
22236  else if(((AVPtr + 1)->FormatType == TimeCmdDescription) && ((AVPtr + 2)->FormatType == TimeLoc)) //change of description then departure
22237  { //added at v2.16.1 so description changes ignored in calculating time to act
22238  LaterStopNumber++;
22239  StopTimeDouble = double((AVPtr + 2)->DepartureTime - AVPtr->ArrivalTime) * 86400.0 / 60.0; //diff will be same for all repeats
22240  // can't convert a TDateTime to a float directly //so repeat times not required
22241  if(TrainID > -1) //exclude trains still to enter
22242  {
22243  TTrain &Train = TrainVectorAtIdent(68, TrainID);
22244  if(TTClockTime > Train.GetTrainTime(71, AVPtr->ArrivalTime)) //running late, added at v2.16.1
22245  {
22246  StopTimeDouble = double(Train.GetTrainTime(72, (AVPtr + 2)->DepartureTime) - TTClockTime) * 86400.0 / 60.0; //may be -ve but if so it's set to 0.5 later
22247  // can't convert a TDateTime to a float directly
22248  }
22249  }
22250  if(StopTimeDouble < 0.5)
22251  {
22252  StopTimeDouble = 0.5;
22253  }
22254  // at least 30 secs delay at station
22255  LaterStopTime += float(StopTimeDouble);
22256  RecoverableTime += StopTimeDouble - 0.5;
22257  if((LaterStopNumber == 1) && (TrainID > -1))
22258  {
22259  TrainVectorAtIdent(69, TrainID).FirstLaterStopRecoverableTime = RecoverableTime;
22260  }
22261  AVPtr++;
22262  AVPtr++;
22263  AVPtr++;
22264  }
22265  else // does something else at the location so no calculation needed
22266  {
22267  Utilities->CallLogPop(2086);
22268  return(-1);
22269  }
22270  }
22271  }
22272  }
22273  NextElement = Track->TrackElementAt(950, CurrentElement).Conn[CurrentExitPos];
22274  if(NextElement == -1) // reached end element, no action needed
22275  {
22276  Utilities->CallLogPop(2077);
22277  return(-1);
22278  }
22279  NextEntryPos = Track->TrackElementAt(919, CurrentElement).ConnLinkPos[CurrentExitPos];
22280  // get NextExitPos
22281  if(Track->TrackElementAt(920, NextElement).TrackType == Points)
22282  {
22283  if((NextEntryPos == 0) || (NextEntryPos == 2))
22284  // leading entry point
22285  {
22286  if(Track->TrackElementAt(921, NextElement).Attribute == 0)
22287  {
22288  NextExitPos = 1;
22289  }
22290  else
22291  {
22292  NextExitPos = 3;
22293  }
22294  }
22295  else
22296  {
22297  NextExitPos = 0; // trailing entry point
22298  }
22299  }
22300  else
22301  {
22302  NextExitPos = Track->GetNonPointsOppositeLinkPos(NextEntryPos);
22303  }
22304  CurrentElement = NextElement;
22305  CurrentEntryPos = NextEntryPos;
22306  CurrentExitPos = NextExitPos;
22307  CurrentExitConfig = Track->TrackElementAt(922, CurrentElement).Config[CurrentExitPos];
22308  CurrentAttribute = Track->TrackElementAt(923, CurrentElement).Attribute;
22309  CurrentElementFailed = Track->TrackElementAt(1550, CurrentElement).Failed; //added at v2.13.2
22310  }
22311  if((CurrentExitConfig == Signal) && (CurrentAttribute == 0))
22312  // ok if autosig route after red signal, no action needed
22313  {
22314  int NextElement = Track->TrackElementAt(924, CurrentElement).Conn[CurrentExitPos];
22315  int NextEntryPos = Track->TrackElementAt(925, CurrentElement).ConnLinkPos[CurrentExitPos];
22316  int RouteNumber; // holder for referenced value, not used
22317  if((AllRoutes->GetRouteTypeAndNumber(31, NextElement, NextEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute) && !CurrentElementFailed)
22318  { //CurrentElementFailed added at v2.13.2
22319  Utilities->CallLogPop(2095);
22320  return(-1);
22321  }
22322  }
22323 
22324  if(TrackSpeedCount > 0)
22325  {
22326  MaxAllowableSpeed = CumTrackSpeed / TrackSpeedCount;
22327  }
22328  else // shouldn't reach here but include to prevent divide by zero error
22329  {
22330  if(CurrentEntryPos > 1)
22331  {
22332  MaxAllowableSpeed = Track->TrackElementAt(1433, CurrentElement).SpeedLimit23;
22333  }
22334  else
22335  {
22336  MaxAllowableSpeed = Track->TrackElementAt(1434, CurrentElement).SpeedLimit01;
22337  }
22338  // Train MaxRunningSpeed taken into account in RebuildOpTimeToActMultimap
22339  }
22340 
22341  if(LaterStopNumber > 0)
22342  {
22343  KmPerLocationStop = float(DistanceToRedSignal) / LaterStopNumber / 1000; // m to km
22344  AvTrackSpeed = (8.75 * KmPerLocationStop) + 44;
22345  }
22346  else
22347  // Av speed calculation based on formula: Speed = 8.75*(kms/location stop) + 44 km/sec (from experiments), subject to a maximum of
22348  // average line speed/2 (for half distance accelerating and half decelerating.
22349  {
22350  AvTrackSpeed = (sqrt(float(DistanceToRedSignal) / 1000) * 44) + 60;
22351  // using linear trendline for accel & decel distance at various speeds
22352  // at half braking, speed never < 60 using this
22353  }
22354  if(AvTrackSpeed > MaxAllowableSpeed)
22355  {
22356  AvTrackSpeed = MaxAllowableSpeed;
22357  }
22358  Utilities->CallLogPop(2096);
22359  return(DistanceToRedSignal);
22360 }
22361 
22362 // ---------------------------------------------------------------------------
22363 // end of TTrainController entries
22364 // ---------------------------------------------------------------------------
TTrain::LinkOccupied
bool LinkOccupied(int Caller, int TrackVectorPosition, int LinkNumber)
Added at v1.2.0: true if any part of train on specific link, false otherwise, including no link prese...
Definition: TrainUnit.cpp:9194
TAllRoutes::TrackIsInARoute
bool TrackIsInARoute(int Caller, int TrackVectorPosition, int LinkPos)
Examines Route2MultiMap and if the element at TrackVectorPosition with LinkPos (can be entry or exit)...
Definition: TrackUnit.cpp:19498
TActionVectorEntry::EventTime
TDateTime EventTime
Definition: TrainUnit.h:133
TTrain::AllowedToPassRedSignal
bool AllowedToPassRedSignal
set when train has been called on, or when under signaller control and instructed to pass a red signa...
Definition: TrainUnit.h:385
JoinedByOther
@ JoinedByOther
Definition: TrainUnit.h:51
TTrainController::CheckShuttleRepeatTime
bool CheckShuttleRepeatTime(int Caller, TDateTime ForwardEventTime, TDateTime ReverseEventTime, int RepeatMinutes)
Check that shuttle link services have consistent times, true for success.
Definition: TrainUnit.cpp:16036
TTrain::ZeroPowerNoNewShuttleFromNonRepeatMessage
bool ZeroPowerNoNewShuttleFromNonRepeatMessage
Definition: TrainUnit.h:351
TTrain::ZeroPowerNoNewServiceMessage
bool ZeroPowerNoNewServiceMessage
Definition: TrainUnit.h:350
TTrain::VOffset
int VOffset[4]
each headcode character is an 8x8 pixel graphic and must be placed within a 16x16 pixel element,...
Definition: TrainUnit.h:498
TTrainController
Handles all train and timetable activities, only one object created.
Definition: TrainUnit.h:712
TTrain::TTrain
TTrain(int Caller, int RearStartElementIn, int RearStartExitPosIn, AnsiString InputCode, int StartSpeed, int Mass, double MaxRunningSpeed, double MaxBrakeRate, double PowerAtRail, TTrainMode TrainMode, TTrainDataEntry *TrainDataEntryPtr, int RepeatNumber, int IncrementalMinutes, int IncrementalDigits, int SignallerMaxSpeed)
Constructor, sets listed member values.
Definition: TrainUnit.cpp:72
TAllRoutes::LockedRouteVector
TLockedRouteVector LockedRouteVector
the vector that stores all the locked routes on the railway
Definition: TrackUnit.h:1742
TRailGraphics::smOrange
Graphics::TBitmap * smOrange
Definition: GraphicUnit.h:904
TActionVector
std::vector< TActionVectorEntry > TActionVector
contains all actions for a single train
Definition: TrainUnit.h:174
TUtilities::LoadFileString
AnsiString LoadFileString(std::ifstream &InFile)
loads a string value from the file
Definition: Utilities.cpp:190
TTrainController::CheckStartPositionValidity
bool CheckStartPositionValidity(int Caller, AnsiString RearElementStr, AnsiString FrontElementStr, bool GiveMessages)
A timetable validation function where train starting positions are checked for validity,...
Definition: TrainUnit.cpp:15698
TTrainDataEntry::FixedDescription
AnsiString FixedDescription
Definition: TrainUnit.h:209
TTrain::ChangeTrainDirection
void ChangeTrainDirection(int Caller, bool NoLogFlag)
Reverses the direction of motion of the train.
Definition: TrainUnit.cpp:6253
TActionVectorEntry::SplitDistribution
AnsiString SplitDistribution
Definition: TrainUnit.h:121
TTrain::MidEntryPos
int MidEntryPos
Definition: TrainUnit.h:373
TTrainController::RebuildOpTimeToActMultimap
void RebuildOpTimeToActMultimap(int Caller)
new v2.2.0 for OperatorActionPanel (OperatorActionPanel changed for ActionsDueForm at v2....
Definition: TrainUnit.cpp:21674
TTrainController::PwrHigh
bool PwrHigh
Definition: TrainUnit.h:822
TTrainController::BuildContinuationTrainExpectationMultiMap
void BuildContinuationTrainExpectationMultiMap(int Caller)
populate the ContinuationTrainExpectationMultiMap during timetable loading
Definition: TrainUnit.cpp:17157
TFixedTrackPiece::GraphicPtr
Graphics::TBitmap * GraphicPtr
the track bitmap for display on the zoomed-in railway
Definition: TrackUnit.h:92
SignallerMoveForwards
@ SignallerMoveForwards
Definition: TrainUnit.h:52
TRailGraphics::CodeR
Graphics::TBitmap * CodeR
Definition: GraphicUnit.h:1017
Create
@ Create
Definition: TrainUnit.h:51
TTrainController::TContinuationEntryVecPosVector
std::vector< int > TContinuationEntryVecPosVector
ensures only one train displayed for a given continuation
Definition: TrainUnit.h:799
TAllRoutes::SetAllRearwardsSignals
void SetAllRearwardsSignals(int Caller, int Attribute, int RouteNumber, int RouteStartPosition)
Set rearwards signals from the specified route starting position.
Definition: TrackUnit.cpp:20505
Arrive
@ Arrive
Definition: TrainUnit.h:51
ChangeDirection
@ ChangeDirection
Definition: TrainUnit.h:51
TTrain::ZeroPowerNoCDTMessage
bool ZeroPowerNoCDTMessage
Definition: TrainUnit.h:349
TTrain::CallingOnFlag
bool CallingOnFlag
calling on permitted
Definition: TrainUnit.h:391
Depart
@ Depart
Definition: TrainUnit.h:51
TRailGraphics::gl89set
Graphics::TBitmap * gl89set
Definition: GraphicUnit.h:721
TTrainController::CheckHeadCodeValidity
bool CheckHeadCodeValidity(int Caller, bool GiveMessages, AnsiString HeadCode)
Returns true if the headcode complies with requirements.
Definition: TrainUnit.cpp:12304
TRailGraphics::gl88set
Graphics::TBitmap * gl88set
Definition: GraphicUnit.h:719
PerfLogForm
TPerfLogForm * PerfLogForm
Definition: PerfLogUnit.cpp:11
clBufferStopBackground
#define clBufferStopBackground
Definition: GraphicUnit.h:292
TTrain::MaxRunningSpeed
double MaxRunningSpeed
the current maximum train running speed
Definition: TrainUnit.h:435
TTrack::BarriersDownVector
TActiveLCVector BarriersDownVector
vector of LCs with barriers down
Definition: TrackUnit.h:804
TPrefDirElement::GetXLinkPos
int GetXLinkPos() const
Returns the XLink array position.
Definition: TrackUnit.h:287
TTrack::IsLCBarrierDownAtHV
bool IsLCBarrierDownAtHV(int Caller, int HLoc, int VLoc)
True if an open (to trains) level crossing is found at H & V.
Definition: TrackUnit.cpp:7483
RestoreTimetableControl
@ RestoreTimetableControl
Definition: TrainUnit.h:52
FailCrashed
@ FailCrashed
Definition: TrainUnit.h:40
TRailGraphics::TempHeadCode
Graphics::TBitmap * TempHeadCode
Definition: GraphicUnit.h:910
TAllRoutes::AutoSigsRoute
@ AutoSigsRoute
Definition: TrackUnit.h:1671
IntermediateSequence
@ IntermediateSequence
Definition: TrainUnit.h:77
TTrack::TSigElement::Attribute
int Attribute
the signal state - red, yellow, double yellow or green
Definition: TrackUnit.h:730
TActionVectorEntry::LocationType
TTimetableLocationType LocationType
indicates where the train is when the relevant action occurs
Definition: TrainUnit.h:139
TTrack::GapFlashGreenPosition
int GapFlashGreenPosition
Definition: TrackUnit.h:786
TAllRoutes::TRouteElementPair
std::pair< int, unsigned int > TRouteElementPair
defines a specific element in a route, the first (int) value is the vector position in the AllRoutesV...
Definition: TrackUnit.h:1683
NamedNonStationLocation
@ NamedNonStationLocation
Definition: TrackUnit.h:67
FinRemHere
@ FinRemHere
Definition: TrainUnit.h:66
TTrain::HeadCodeGrPtr
Graphics::TBitmap * HeadCodeGrPtr[4]
points to the headcode segment graphics e.g. 5,A,4,7.
Definition: TrainUnit.h:515
TTrainController::TContinuationTrainExpectationEntry::TrainDataEntryPtr
TTrainDataEntry * TrainDataEntryPtr
points to the service entry in the timetable's TrainDataVector
Definition: TrainUnit.h:760
FSHNewService
@ FSHNewService
Definition: TrainUnit.h:67
TTrain::CoastingBrakeRate
double CoastingBrakeRate
the train brake rate when coasting
Definition: TrainUnit.h:443
TTrain::MaxBrakeRate
double MaxBrakeRate
the maximum brake rate that the train can achieve
Definition: TrainUnit.h:439
TTimetableLocationType
TTimetableLocationType
Definition: TrainUnit.h:71
TAllRoutes::DiagonalFouledByRoute
bool DiagonalFouledByRoute(int Caller, int HLoc, int VLoc, int DiagonalLinkNumber)
As above but only checks for a route (may or may not be a train present (new at v1....
Definition: TrackUnit.cpp:21266
TTrainController::CheckForDuplicateCrossReferences
bool CheckForDuplicateCrossReferences(int Caller, AnsiString MainHeadCode, AnsiString SecondHeadCode, bool GiveMessages)
A timetable validation function where referenced services are checked for uniqueness,...
Definition: TrainUnit.cpp:14931
TTrackElement::StationEntryStopLinkPos2
int StationEntryStopLinkPos2
Definition: TrackUnit.h:153
TTrain::MidExitPos
int MidExitPos
Definition: TrainUnit.h:373
TRailGraphics::CodeD
Graphics::TBitmap * CodeD
Definition: GraphicUnit.h:1003
TTrain::TrainFailed
bool TrainFailed
Definition: TrainUnit.h:419
TUtilities::CheckFileStringZeroDelimiter
bool CheckFileStringZeroDelimiter(std::ifstream &InFile)
checks that the value is a string ('0' only accepted as the delimiter), returns true for success
Definition: Utilities.cpp:435
TTrack::TTrackVectorIterator
std::vector< TTrackElement >::iterator TTrackVectorIterator
iterator for TTrackVector
Definition: TrackUnit.h:652
TRailGraphics::Code_w
Graphics::TBitmap * Code_w
Definition: GraphicUnit.h:986
FailDerailed
@ FailDerailed
Definition: TrainUnit.h:40
TTrainController::SPADWarning
bool SPADWarning
Definition: TrainUnit.h:808
TTrain::ZeroPowerNoFrontSplitMessage
bool ZeroPowerNoFrontSplitMessage
Definition: TrainUnit.h:345
TRailGraphics::Code_s
Graphics::TBitmap * Code_s
Definition: GraphicUnit.h:982
TTrainController::GetExitLocationAndAt
AnsiString GetExitLocationAndAt(int Caller, TNumList &ExitList, AnsiString &AllowedExits) const
Check all timetable names in ExitList, if all same return " at [name]" + AllowableExits = elements,...
Definition: TrainUnit.cpp:20962
TTrain::SaveOneSessionTrain
void SaveOneSessionTrain(int Caller, std::ofstream &OutFile)
Data for a single train is saved to a session file.
Definition: TrainUnit.cpp:8070
TTrain::DepartureTimeSet
bool DepartureTimeSet
set when stopped at a location and the next action is departure (set in UpdateTrain when ReleaseTime ...
Definition: TrainUnit.h:393
TTrain::Plotted
bool Plotted
set when train plotted on screen
Definition: TrainUnit.h:487
TTrain::LagElement
int LagElement
Definition: TrainUnit.h:373
TTrain::RemainHere
void RemainHere(int Caller)
Sends the 'train terminated' message to the performance log and sets TimetableFinished to true.
Definition: TrainUnit.cpp:6390
TTrackElement::StationEntryStopLinkPos3
int StationEntryStopLinkPos3
Definition: TrackUnit.h:153
TTrainController::BufferAttentionWarning
bool BufferAttentionWarning
Definition: TrainUnit.h:808
TTrainController::TrainVectorAtIdent
TTrain & TrainVectorAtIdent(int Caller, int TrainID)
Return a reference to the train with ID TrainID, carries out validity checking on TrainID.
Definition: TrainUnit.cpp:10479
TPrefDirElement::GetRouteEXGraphicPtr
Graphics::TBitmap * GetRouteEXGraphicPtr()
Returns route graphic.
Definition: TrackUnit.h:323
TRailGraphics::smLightBlue
Graphics::TBitmap * smLightBlue
Definition: GraphicUnit.h:901
TTrainController::TContinuationAutoSigEntry::RouteNumber
int RouteNumber
the AllRoutesVector position of the route
Definition: TrainUnit.h:734
TTrainController::StopTTClockMessage
void StopTTClockMessage(int Caller, AnsiString Message)
sends a message to the user and stops the timetable clock while it is displayed
Definition: TrainUnit.cpp:16888
TExitInfo::TimeToExitSecs
short TimeToExitSecs
Definition: TrainUnit.h:107
TTrainController::OperatingTrainLateArr
int OperatingTrainLateArr
< all these set to 0 in constructor
Definition: TrainUnit.h:866
TRailGraphics::CodeJ
Graphics::TBitmap * CodeJ
Definition: GraphicUnit.h:1009
TUtilities::IncrementAnsiTimeOneMinute
AnsiString IncrementAnsiTimeOneMinute(AnsiString TimeVal)
takes "HH:MM" and increments it to "HH:MX", where MX == MM + 1, incrementing the hour if necessary
Definition: Utilities.cpp:826
TRailGraphics::CodeQ
Graphics::TBitmap * CodeQ
Definition: GraphicUnit.h:1016
TDisplay::GetOutputLog9
TLabel * GetOutputLog9()
Definition: DisplayUnit.h:185
TAllRoutes::RemoveRouteElement
void RemoveRouteElement(int Caller, int HLoc, int VLoc, int ELink)
Erases the route element from Route2MultiMap and from the PrefDirVector.
Definition: TrackUnit.cpp:20294
TTrainController::CrashWarning
bool CrashWarning
Definition: TrainUnit.h:808
TTrainController::CheckLocationValidity
bool CheckLocationValidity(int Caller, AnsiString LocStr, bool GiveMessages, bool CheckLocationsExistInRailway)
Returns true if the location name complies with requirements.
Definition: TrainUnit.cpp:12254
TAllRoutes::TLockedRouteClass::LastXLinkPos
int LastXLinkPos
the XLinkPos value of the last (i.e. most forward) element in the route
Definition: TrackUnit.h:1663
TTrain::PlotTrain
void PlotTrain(int Caller, TDisplay *Disp)
Plots the train on the display in normal (zoomed-in) mode.
Definition: TrainUnit.cpp:9166
TNumListIterator
TNumList::iterator TNumListIterator
Definition: TrainUnit.h:95
FailLevelCrossingCrash
@ FailLevelCrossingCrash
Definition: TrainUnit.h:42
TActionVectorEntry::DepartureTime
TDateTime DepartureTime
relevant times at which the action is timetabled, zeroed on creation so change to -1 as a marker for ...
Definition: TrainUnit.h:133
TRailGraphics::Code_f
Graphics::TBitmap * Code_f
Definition: GraphicUnit.h:969
FailCreateLockedRoute
@ FailCreateLockedRoute
Definition: TrainUnit.h:41
TTrainController::TContinuationTrainExpectationEntry::IncrementalDigits
int IncrementalDigits
Repeat headcode separation.
Definition: TrainUnit.h:756
TTrainController::SaveSessionTrains
void SaveSessionTrains(int Caller, std::ofstream &SessionFile)
save trains to a session file
Definition: TrainUnit.cpp:16903
TOneCompleteFormattedTrain::HeadCode
AnsiString HeadCode
Definition: TrainUnit.h:267
TRailGraphics::Code_d
Graphics::TBitmap * Code_d
Definition: GraphicUnit.h:967
TTrain::JoinedBy
void JoinedBy(int Caller)
Carry out the actions needed when a train is waiting to be joined by another train.
Definition: TrainUnit.cpp:6173
FailIncorrectExit
@ FailIncorrectExit
Definition: TrainUnit.h:42
TRailGraphics::Code_t
Graphics::TBitmap * Code_t
Definition: GraphicUnit.h:983
TTrainController::GetServiceFromVector
TTrainDataEntry GetServiceFromVector(AnsiString Caller, AnsiString HeadCode, TTrainDataVector Vector, bool &FinishType, bool &FoundFlag)
Return the TrainDataVector entry corresponding to ServiceReference, FinishType is 0 for end of servic...
Definition: TrainUnit.cpp:20318
TRailGraphics::ChangeBackgroundColour
void ChangeBackgroundColour(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor NewBackgroundColour, TColor OldBackgroundColour, bool &ColourError)
Definition: GraphicUnit.cpp:3593
TTrain::GetLeadElement
int GetLeadElement()
get LeadElement - used in RouteLockingRequired in TrackUnit.cpp
Definition: TrainUnit.h:694
TimeCmd
@ TimeCmd
Definition: TrainUnit.h:66
TTrain::BackgroundPtr
Graphics::TBitmap * BackgroundPtr[4]
the existing track graphic that the train headcode segment covers up (one for each headcode segment)
Definition: TrainUnit.h:511
TUtilities::LoadFileDouble
double LoadFileDouble(std::ifstream &InFile)
loads a double value from the file (converts from a string to a double) and uses the local decimal po...
Definition: Utilities.cpp:172
TTrain::ZeroPowerDepartMessage
bool ZeroPowerDepartMessage
Definition: TrainUnit.h:354
TTrain::DistanceToStationStop
int DistanceToStationStop
calculated in UpdateTrain & used in CalcDistanceToRedSignalandStopTime to cater for trains running ea...
Definition: TrainUnit.h:469
TTrainController::EntryPos
int EntryPos(int Caller, int TrainIDIn, int TrackVectorNumber)
Return the track entry link (Link[]) array position for the given train on track element at track vec...
Definition: TrainUnit.cpp:10441
SignallerLeave
@ SignallerLeave
Definition: TrainUnit.h:53
TTrain::PlotEntryPos
int PlotEntryPos[4]
the LinkPos value corresponding to the train entry link of the element where each of the 4 headcode c...
Definition: TrainUnit.h:504
NotStarted
@ NotStarted
Definition: TrainUnit.h:88
TTrack::OneNamedLocationElementAtLocation
bool OneNamedLocationElementAtLocation(int Caller, AnsiString LocationName)
True if there is at least one named location element with name 'LocationName', used in timetable inte...
Definition: TrackUnit.cpp:11516
TTrainController::SPADEvents
int SPADEvents
Definition: TrainUnit.h:860
TTrainController::LastTTTime
AnsiString LastTTTime
Stores the last time used in the timetable as an AnsiString - used for timetable analysis.
Definition: TrainUnit.h:802
TTrack::OneStationLongEnoughForSplit
bool OneStationLongEnoughForSplit(int Caller, AnsiString LocationName)
Definition: TrackUnit.cpp:10990
TTrain::ZeroPowerNoRepeatShuttleMessage
bool ZeroPowerNoRepeatShuttleMessage
Definition: TrainUnit.h:352
TTrackElement::SigAspect
enum TTrackElement::@1 SigAspect
TRailGraphics::gl90set
Graphics::TBitmap * gl90set
Definition: GraphicUnit.h:724
TTrain::ActionVectorEntryPtr
TActionVectorEntry * ActionVectorEntryPtr
points to the current position in the ActionVector (a member of the TTrainDataEntry class)
Definition: TrainUnit.h:379
TTrainDataEntry
Contains all data for a single timetable service entry.
Definition: TrainUnit.h:207
LeadMid
@ LeadMid
Definition: TrainUnit.h:300
FailMissedPass
@ FailMissedPass
Definition: TrainUnit.h:41
clSignalStopBackground
#define clSignalStopBackground
Definition: GraphicUnit.h:300
TTrainController::LateDeps
int LateDeps
Definition: TrainUnit.h:850
TAllRoutes::IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber
bool IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber(int Caller, int TrackVectorPosition, int XLinkPos, TPrefDirElement &PrefDirElement, int &LockedVectorNumber)
Checks whether the preferred direction element at TrackVectorPosition with XLinkPos value is in a loc...
Definition: TrackUnit.cpp:20779
TrackUnit.h
TTrainController::CheckStartAllowable
bool CheckStartAllowable(int Caller, int RearPosition, int RearExitPos, AnsiString HeadCode, bool ReportFlag, TActionEventType &EventType)
Called when trying to introduce a new train - checks for points in correct orientation,...
Definition: TrainUnit.cpp:15782
TTrack::ResetAllTrainIDsAndFailedPointOrigSpeedLimits
void ResetAllTrainIDsAndFailedPointOrigSpeedLimits(int Caller)
Definition: TrackUnit.cpp:7855
TTrainController::RebuildTimeToExitMultiMap
void RebuildTimeToExitMultiMap(int Caller)
new for multiplayer
Definition: TrainUnit.cpp:21845
TRailGraphics::Code_r
Graphics::TBitmap * Code_r
Definition: GraphicUnit.h:981
TTrain::SendMissedActionLogs
void SendMissedActionLogs(int Caller, int IncNum, TActionVectorEntry *Ptr)
Missed actions (see NameInTimetableBeforeCDT above) sent to the performance log and performance file.
Definition: TrainUnit.cpp:6428
TOneRoute::RouteID
int RouteID
the ID number of the route, this is needed for session saves
Definition: TrackUnit.h:1558
TTrainController::TContinuationTrainExpectationEntry::RepeatNumber
int RepeatNumber
service RepeatNumber
Definition: TrainUnit.h:752
TRailGraphics::CodeX
Graphics::TBitmap * CodeX
Definition: GraphicUnit.h:1023
TOneTrainFormattedEntry::Time
AnsiString Time
the time of the action as a string
Definition: TrainUnit.h:254
TRailGraphics::CodeT
Graphics::TBitmap * CodeT
Definition: GraphicUnit.h:1019
TTrainDataEntry::PowerAtRail
double PowerAtRail
in Watts (taken as 80% of the train's Gross Power, i.e. that entered by the user)
Definition: TrainUnit.h:217
TTrainController::LastTrainLoaded
int LastTrainLoaded
displays last train loaded from session file, used for debugging
Definition: TrainUnit.h:870
TTrainController::NotStartedTrainLateMins
float NotStartedTrainLateMins
total late minutes of trains that haven't started yet on exit operation for locations not reached yet
Definition: TrainUnit.h:827
Utilities.h
TTrainController::TContinuationAutoSigVectorIterator
TContinuationAutoSigVector::iterator TContinuationAutoSigVectorIterator
Definition: TrainUnit.h:742
TTrain::SkippedDeparture
bool SkippedDeparture
< used for terminating a service early and becoming new follow-on service
Definition: TrainUnit.h:333
TTrain::TrainOnContinuation
bool TrainOnContinuation(int Caller)
Returns true if any part of train on a continuation - called when checking for failures,...
Definition: TrainUnit.cpp:9643
ChangeDescription
@ ChangeDescription
Definition: TrainUnit.h:54
TTrainController::CheckSessionLockedRoutes
bool CheckSessionLockedRoutes(int Caller, std::ifstream &SessionFile)
Part of the session file integrity check for locked routes, true for success.
Definition: TrainUnit.cpp:17006
FailTrainEntry
@ FailTrainEntry
Definition: TrainUnit.h:39
MidLag
@ MidLag
Definition: TrainUnit.h:300
TDisplay::Update
void Update()
Repaint the screen display.
Definition: DisplayUnit.h:222
TTrainController::LocServiceTimesDepTimeSort
bool LocServiceTimesDepTimeSort(TLocServiceTimes i, TLocServiceTimes j)
Definition: TrainUnit.h:907
TTrainController::CheckSessionTrains
bool CheckSessionTrains(int Caller, std::ifstream &InFile)
Part of the session file integrity check for train entries, true for success.
Definition: TrainUnit.cpp:16944
StartNew
@ StartNew
Definition: TrainUnit.h:66
TakeSignallerControl
@ TakeSignallerControl
Definition: TrainUnit.h:51
LeadMidLag
@ LeadMidLag
Definition: TrainUnit.h:300
TTrack::GapFlashGreen
TGraphicElement * GapFlashGreen
Definition: TrackUnit.h:808
TTrain::ZeroPowerNoRepeatShuttleOrNewServiceMessage
bool ZeroPowerNoRepeatShuttleOrNewServiceMessage
Definition: TrainUnit.h:353
TrainFailure
@ TrainFailure
Definition: TrainUnit.h:52
TTrain::MinsDelayed
float MinsDelayed
new at v2.2.0 for operator time to act panel. Calculated in UpdateTrain
Definition: TrainUnit.h:457
TTrack::TSigElement::SigPtr
Graphics::TBitmap * SigPtr
pointer to the graphic
Definition: TrackUnit.h:732
TTrainController::TimeToExitMultiMap
TTimeToExitMultiMap TimeToExitMultiMap
Map of times to exit & exit coordinates.
Definition: TrainUnit.h:1013
TAllRoutes::FindRouteNumberFromRoute2MultiMapNoErrors
bool FindRouteNumberFromRoute2MultiMapNoErrors(int Caller, int HLoc, int VLoc, int ELink, int &RouteNumber)
If a route is present at H, V & Elink returns true with RouteNumber giving vector position in AllRout...
Definition: TrackUnit.cpp:20042
TTrainController::Last2CharactersBothDigits
bool Last2CharactersBothDigits(int Caller, AnsiString HeadCode)
Checks the last two characters in HeadCode and returns true if both are digits.
Definition: TrainUnit.cpp:11778
TRailGraphics::Code2
Graphics::TBitmap * Code2
Definition: GraphicUnit.h:992
TTrack::GetVectorPositionsFromInactiveTrackMap
TIMPair GetVectorPositionsFromInactiveTrackMap(int Caller, int HLoc, int VLoc, bool &FoundFlag)
Similar to GetVectorPositionFromTrackMap but for inactive elements, a pair is returned because there ...
Definition: TrackUnit.cpp:5963
TTrain::BackgroundColour
TColor BackgroundColour
the background colour of the train's headcode graphics
Definition: TrainUnit.h:518
TTrainOperatingData::EventReported
TActionEventType EventReported
Definition: TrainUnit.h:186
TTrain
Definition: TrainUnit.h:306
TRailGraphics::Code_z
Graphics::TBitmap * Code_z
Definition: GraphicUnit.h:989
TTrainController::CheckTimeValidity
bool CheckTimeValidity(int Caller, AnsiString TimeStr, TDateTime &Time)
returns true if the time complies with requirements
Definition: TrainUnit.cpp:11797
clSignallerStopped
#define clSignallerStopped
Definition: GraphicUnit.h:299
TOnePrefDir::GetFixedPrefDirElementAt
const TPrefDirElement & GetFixedPrefDirElementAt(int Caller, int At) const
Return a non-modifiable element at PrefDirVector position 'At'.
Definition: TrackUnit.cpp:12252
FailBuffersPreventingStart
@ FailBuffersPreventingStart
Definition: TrainUnit.h:42
TTrainController::LocServiceTimesArrTimeSort
bool LocServiceTimesArrTimeSort(TLocServiceTimes i, TLocServiceTimes j)
Definition: TrainUnit.h:902
TOneTrainFormattedEntry::Action
AnsiString Action
includes location if relevant
Definition: TrainUnit.h:252
TTrainController::ContinuationTrainExpectationMultiMap
TContinuationTrainExpectationMultiMap ContinuationTrainExpectationMultiMap
Multimap for TContinuationTrainExpectationEntry objects, the access key is the expectation time.
Definition: TrainUnit.h:884
TPerfLogForm::PerformanceLog
void PerformanceLog(int Caller, AnsiString Statement)
Send Statement to the performance log on screen and to the file.
Definition: PerfLogUnit.cpp:32
TTrain::StoppedForTrainInFront
bool StoppedForTrainInFront
Definition: TrainUnit.h:493
GapJump
@ GapJump
Definition: TrackUnit.h:66
TRailGraphics::CodeK
Graphics::TBitmap * CodeK
Definition: GraphicUnit.h:1010
TTrainController::MissedStops
int MissedStops
Definition: TrainUnit.h:853
NoSequence
@ NoSequence
Definition: TrainUnit.h:77
clCallOnBackground
#define clCallOnBackground
Definition: GraphicUnit.h:293
TTrackElement::Length01
int Length01
Definition: TrackUnit.h:151
TTrackElement::SpeedLimit01
int SpeedLimit01
Definition: TrackUnit.h:151
TTrain::TerminatedMessageSent
bool TerminatedMessageSent
set when a 'train terminated' message has been logged, to prevent its being logged more than once
Definition: TrainUnit.h:415
TTrain::FinishJoin
void FinishJoin(int Caller)
Carry out the actions needed when a train is waiting to join another train.
Definition: TrainUnit.cpp:6124
TTrainController::FinishedOperation
void FinishedOperation(int Caller)
called when exiting operation mode to delete all trains and timetable data etc
Definition: TrainUnit.cpp:10072
TTrain::MidElement
int MidElement
Definition: TrainUnit.h:373
TAllRoutes::CallonVector
std::vector< TCallonEntry > CallonVector
the store of all call-on entries
Definition: TrackUnit.h:1710
TTrain::LeadElement
int LeadElement
Definition: TrainUnit.h:373
clDerailedBackground
#define clDerailedBackground
Definition: GraphicUnit.h:295
TTrain::RepeatNumber
int RepeatNumber
indicates which of the repeating services this train represents (0 = first service)
Definition: TrainUnit.h:367
TTrainDataEntry::ActionVector
TActionVector ActionVector
all the actions for the train
Definition: TrainUnit.h:227
TTrainController::BaseTime
TDateTime BaseTime
CurrentDateTime (i.e. real time) when operation restarts after a pause.
Definition: TrainUnit.h:715
TTrain::StartSpeed
int StartSpeed
the speed of the train when introduced into the railway (in km/h)
Definition: TrainUnit.h:371
TExitInfo::TExitInfo
TExitInfo()
Definition: TrainUnit.cpp:63
TTrainController::LoadSessionLockedRoutes
void LoadSessionLockedRoutes(int Caller, std::ifstream &SessionFile)
load locked routes from a session file
Definition: TrainUnit.cpp:16985
TTrainController::EarlyPasses
int EarlyPasses
Definition: TrainUnit.h:846
TTrain::HeadCode
AnsiString HeadCode
Definition: TrainUnit.h:329
TDisplay::GetOutputLog7
TLabel * GetOutputLog7()
Definition: DisplayUnit.h:175
TTrain::TrainInFront
bool TrainInFront
Definition: TrainUnit.h:494
TTrainController::TOpTimeToActMultiMapEntry
std::pair< float, THCandTrainPosParam > TOpTimeToActMultiMapEntry
Definition: TrainUnit.h:797
FailMissedSplit
@ FailMissedSplit
Definition: TrainUnit.h:40
TTrain::EntryTime
TDateTime EntryTime
Definition: TrainUnit.h:473
TExitInfo::RepeatNumber
short RepeatNumber
Definition: TrainUnit.h:106
TTrainController::TContinuationAutoSigEntry::FirstDelay
double FirstDelay
Definition: TrainUnit.h:730
TTrain::LastSigPassedFailed
bool LastSigPassedFailed
flag used to erase route elements in an autosigs route after a failed signal
Definition: TrainUnit.h:401
TTrainController::SaveTrainDataVectorToFile
void SaveTrainDataVectorToFile(int Caller)
diagnostic function to store all train data to a file for examination, not used normally
Definition: TrainUnit.cpp:16708
TOnePrefDir::PrefDirSize
unsigned int PrefDirSize() const
Return the vector size.
Definition: TrackUnit.h:1416
TTrack::PlotSignal
void PlotSignal(int Caller, TTrackElement TrackElement, TDisplay *Disp)
Plot signals on screen according to their aspect (Attribute value)
Definition: TrackUnit.cpp:6251
End
@ End
Definition: TrackUnit.h:76
TUtilities::FixedMinRepairTime
int FixedMinRepairTime
Definition: Utilities.h:72
TTrain::OneLengthAccelDecel
bool OneLengthAccelDecel
set when a train can only move forwards one element before stopping but needs to accelerate for the f...
Definition: TrainUnit.h:405
TTrain::FloatingLabelNextString
AnsiString FloatingLabelNextString(int Caller, TActionVectorEntry *Ptr)
Used in the floating window to display the 'Next' action.
Definition: TrainUnit.cpp:7072
TTrack::TimetabledLocationNameAllocated
bool TimetabledLocationNameAllocated(int Caller, AnsiString LocationName)
True if a non-empty LocationName found as a timetabled location name i.e. not as a continuation name.
Definition: TrackUnit.cpp:9057
TTrain::FailedTrainNoFinishJoinMessage
bool FailedTrainNoFinishJoinMessage
Definition: TrainUnit.h:347
TTrack::InactiveTrackElementAt
TTrackElement & InactiveTrackElementAt(int Caller, int At)
A range-checked version of InactiveTrackVector.at(At)
Definition: TrackUnit.cpp:10955
ExitRailway
@ ExitRailway
Definition: TrainUnit.h:67
TPrefDirElement::GetTrackVectorPosition
unsigned int GetTrackVectorPosition() const
Returns TrackVectorPosition.
Definition: TrackUnit.h:305
TTrain::StationStopCalculated
bool StationStopCalculated
used in calculating DistanceToStationStop for trains running early before they have reached the stop ...
Definition: TrainUnit.h:411
TTrainController::LoadSessionContinuationAutoSigEntries
void LoadSessionContinuationAutoSigEntries(int Caller, std::ifstream &SessionFile)
load ContinuationAutoSigEntries from a session file
Definition: TrainUnit.cpp:17068
FailMissedDSC
@ FailMissedDSC
Definition: TrainUnit.h:40
TTrackElement
Basic track elements as implemented in the overall railway layout.
Definition: TrackUnit.h:125
TRailGraphics::Code_n
Graphics::TBitmap * Code_n
Definition: GraphicUnit.h:977
FailMissedNewService
@ FailMissedNewService
Definition: TrainUnit.h:41
TUtilities::Format96HHMMSS
AnsiString Format96HHMMSS(TDateTime DateTime)
formats a TDateTime into an AnsiString of the form hh:mm:ss where hh runs from 00 to 95 & resets when...
Definition: Utilities.cpp:788
TRailGraphics::smSolidBgnd
Graphics::TBitmap * smSolidBgnd
Definition: GraphicUnit.h:1027
clTRSBackground
#define clTRSBackground
Definition: GraphicUnit.h:304
TTrainController::OtherMissedEvents
int OtherMissedEvents
Definition: TrainUnit.h:858
TTrainController::EarlyExits
int EarlyExits
Definition: TrainUnit.h:847
TTrain::ExitSpeedHalf
double ExitSpeedHalf
speed when half way into the next element
Definition: TrainUnit.h:429
SignalPost
@ SignalPost
Definition: TrackUnit.h:66
TRailGraphics::Code_h
Graphics::TBitmap * Code_h
Definition: GraphicUnit.h:971
TTrain::LastActionTime
TDateTime LastActionTime
time of the last timetabled event, used to ensure at least a 30 second delay before the next action
Definition: TrainUnit.h:477
TDisplay::GetOutputLog1
TLabel * GetOutputLog1()
Return pointers to warning message logs (appear above the railway display during operation)
Definition: DisplayUnit.h:144
TAllRoutes::TRoute2MultiMapIterator
TRoute2MultiMap::iterator TRoute2MultiMapIterator
Definition: TrackUnit.h:1687
TTrain::TrainFailurePending
bool TrainFailurePending
set when failure due & takes effect when all PlotElements properly set, added at v2....
Definition: TrainUnit.h:357
FailMissedTerminate
@ FailMissedTerminate
Definition: TrainUnit.h:41
TTrainController::SecondPassActions
bool SecondPassActions(int Caller, bool GiveMessages, bool &TwoLocationFlag)
Carry out further detailed timetable consistency checks, return true for success.
Definition: TrainUnit.cpp:12947
TRailGraphics::Code9
Graphics::TBitmap * Code9
Definition: GraphicUnit.h:999
TTrainDataEntry::HeadCode
AnsiString HeadCode
Definition: TrainUnit.h:209
FailTrainInFront
@ FailTrainInFront
Definition: TrainUnit.h:43
TTrain::TrainHasFailed
void TrainHasFailed(int Caller)
Called when there is a random train failure.
Definition: TrainUnit.cpp:5752
TUtilities::MinorDelayFactor
float MinorDelayFactor
Definition: Utilities.h:53
clNormalBackground
#define clNormalBackground
Definition: GraphicUnit.h:298
TTrainController::CheckNonRepeatingShuttleLinksAndSetData
bool CheckNonRepeatingShuttleLinksAndSetData(int Caller, AnsiString MainHeadCode, AnsiString NonRepeatingHeadCode, bool SetDataAndCheckLocations, bool GiveMessages)
A timetable validation function where cross references are checked for validity for non-repeating shu...
Definition: TrainUnit.cpp:16059
TTrainController::CheckFourthValidityForSplit
bool CheckFourthValidityForSplit(AnsiString SplitDistributionString, bool GiveMessages)
Checks fourth segment in timetable for train splits - percentage mass then '-' then percentage power ...
Definition: TrainUnit.cpp:12209
TActionVectorEntry::NonRepeatingShuttleLinkEntryPtr
TTrainDataEntry * NonRepeatingShuttleLinkEntryPtr
pointer used by shuttles for the non-shuttle train links, in & out, the corresponding non-shuttle lin...
Definition: TrainUnit.h:147
TTrainController::SPADRisks
int SPADRisks
Definition: TrainUnit.h:861
TTrainController::TLocServiceTimesVector
std::vector< TLocServiceTimes > TLocServiceTimesVector
Definition: TrainUnit.h:783
TTrainController::LocServiceTimesAtLocTimeSort
bool LocServiceTimesAtLocTimeSort(TLocServiceTimes i, TLocServiceTimes j)
Definition: TrainUnit.h:911
TTrain::EntrySpeed
double EntrySpeed
speed at which the train enters the next element
Definition: TrainUnit.h:427
TConfiguration
TConfiguration
< describes the type of track link. 'End' is used for both buffer stop and continuation entry/exit po...
Definition: TrackUnit.h:75
TTrain::TrainInFrontMessage
bool TrainInFrontMessage
flags to indicate whether the respective message has been sent
Definition: TrainUnit.h:355
TTrain::TrainGone
bool TrainGone
set when train has left the railway, so it can be removed from the display at the next clock tick
Definition: TrainUnit.h:489
TTrackType
TTrackType
< describes the type of track element
Definition: TrackUnit.h:65
TTrainController::OnTimePasses
int OnTimePasses
Definition: TrainUnit.h:856
TTrainController::SigSHigh
bool SigSHigh
Definition: TrainUnit.h:822
TRailGraphics::smCaramel
Graphics::TBitmap * smCaramel
Definition: GraphicUnit.h:898
TGraphicElement::PlotOriginal
void PlotOriginal(int Caller, TDisplay *Disp)
Plot the original graphic on screen.
Definition: TrackUnit.cpp:1927
TExitInfo
Definition: TrainUnit.h:103
clBufferAttentionNeeded
#define clBufferAttentionNeeded
Definition: GraphicUnit.h:291
TUtilities::ModerateDelayCutoff
float ModerateDelayCutoff
Definition: Utilities.h:51
TTrain::StoppedAfterSPAD
bool StoppedAfterSPAD
Definition: TrainUnit.h:493
FailSPAD
@ FailSPAD
Definition: TrainUnit.h:39
TTrainController::OnTimeArrivals
int OnTimeArrivals
Definition: TrainUnit.h:854
clTrainFailedBackground
#define clTrainFailedBackground
Definition: GraphicUnit.h:305
TRailGraphics::Code_x
Graphics::TBitmap * Code_x
Definition: GraphicUnit.h:987
clFrontCodeSignaller
#define clFrontCodeSignaller
Definition: GraphicUnit.h:296
TimeCmdHeadCode
@ TimeCmdHeadCode
Definition: TrainUnit.h:66
Utilities
TUtilities * Utilities
Definition: Utilities.cpp:47
TFixedTrackPiece::SmallGraphicPtr
Graphics::TBitmap * SmallGraphicPtr
the track bitmap for display on the zoomed-out railway
Definition: TrackUnit.h:94
TActionVectorEntry::ArrivalTime
TDateTime ArrivalTime
Definition: TrainUnit.h:133
TTrain::StoppedAtBuffers
bool StoppedAtBuffers
Definition: TrainUnit.h:493
TTrainController::CreateTTAnalysisFile
bool CreateTTAnalysisFile(int Caller, AnsiString RailwayTitle, AnsiString TimetableTitle, AnsiString CurDir, bool ArrChecked, bool DepChecked, bool AtLocChecked, bool DirChecked, int ArrRange, int DepRange)
Generate a timetable analysis file in the 'Formatted Timetables' folder, return false if failed for a...
Definition: TrainUnit.cpp:17884
TRailGraphics::Code_b
Graphics::TBitmap * Code_b
Definition: GraphicUnit.h:965
TTrain::Mass
int Mass
in kg
Definition: TrainUnit.h:467
TRailGraphics::Code0
Graphics::TBitmap * Code0
Definition: GraphicUnit.h:990
TTrainController::ProcessOneTimetableLine
bool ProcessOneTimetableLine(int Caller, int Count, AnsiString OneLine, bool &EndOfFile, bool FinalCall, bool GiveMessages, bool CheckLocationsExistInRailway)
Carry out preliminary (mainly syntax) validity checks on a single timetable service entry and (if Fin...
Definition: TrainUnit.cpp:11041
TTrainController::TContinuationTrainExpectationEntry::IncrementalMinutes
int IncrementalMinutes
Repeat separation in minutes.
Definition: TrainUnit.h:754
TTrain::LeadExitPos
int LeadExitPos
Definition: TrainUnit.h:373
TTrain::FrontElementLength
int FrontElementLength
values associated with the element immediately in front of the train (speed in km/h,...
Definition: TrainUnit.h:465
TTimetableShuttleLinkType
TTimetableShuttleLinkType
Definition: TrainUnit.h:81
TRailGraphics::CodeP
Graphics::TBitmap * CodeP
Definition: GraphicUnit.h:1015
Pass
@ Pass
Definition: TrainUnit.h:53
TTrain::SkipPtrValue
int SkipPtrValue
stores the pointer increment from first action in ActionVector for skipped actions when a departure i...
Definition: TrainUnit.h:381
TNumList
std::list< int > TNumList
a list of valid train exit TrackVector positions for 'Fer' entries
Definition: TrainUnit.h:91
TTrainController::DerailWarning
bool DerailWarning
Definition: TrainUnit.h:808
TTrainController::TContinuationTrainExpectationMultiMapPair
std::pair< TDateTime, TContinuationTrainExpectationEntry > TContinuationTrainExpectationMultiMapPair
a single multimap entry
Definition: TrainUnit.h:768
RouteForceCancelled
@ RouteForceCancelled
Definition: TrainUnit.h:43
EnRoute
@ EnRoute
Definition: TrainUnit.h:72
TTrain::IsTrainIDOnBridgeTrackPos23
bool IsTrainIDOnBridgeTrackPos23(int Caller, unsigned int TrackVectorPosition)
True if train is on a bridge on trackpos 2 & 3.
Definition: TrainUnit.cpp:3284
TTrack::DiagonalFouledByTrain
bool DiagonalFouledByTrain(int Caller, int HLoc, int VLoc, int DiagonalLinkNumber, int &TrainID)
As DiagonalFouledByRouteOrTrain (in TAllRoutes) but only checks for a train (may or may not be a rout...
Definition: TrackUnit.cpp:11964
TTrainController::TotLateDepMins
float TotLateDepMins
Definition: TrainUnit.h:838
TDisplay::ZoomOutFlag
bool ZoomOutFlag
true when zoomed-out
Definition: DisplayUnit.h:70
TRailGraphics::CodeC
Graphics::TBitmap * CodeC
Definition: GraphicUnit.h:1002
TTrain::PlotElement
int PlotElement[4]
the TrackVectorPosition of the element where each of the 4 headcode characters is plotted (need to be...
Definition: TrainUnit.h:502
TTrainController::TServiceCallingLocsList
std::list< AnsiString > TServiceCallingLocsList
Used in determining train directions in timetable conflict analysis.
Definition: TrainUnit.h:786
TTrainController::TrainVector
TTrainVector TrainVector
vector containing all trains currently in the railway
Definition: TrainUnit.h:892
TUtilities::ModerateDelayFactor
float ModerateDelayFactor
Definition: Utilities.h:54
TUtilities::MajorDelayCutoff
float MajorDelayCutoff
Definition: Utilities.h:52
TRailGraphics::Code_l
Graphics::TBitmap * Code_l
Definition: GraphicUnit.h:975
TTrainController::TContinuationAutoSigEntry
< TTClockTime when last session saved - to prevent display of warning message on exit session if < 5 ...
Definition: TrainUnit.h:728
TUtilities::CallLogPop
void CallLogPop(int Caller)
pops the last entry off the call stack, throws an error if called when empty
Definition: Utilities.cpp:50
TTrain::IncrementalMinutes
int IncrementalMinutes
the number of minutes to increment by in repeat entries
Definition: TrainUnit.h:361
TTrain::MaximumSpeedLimit
static const int MaximumSpeedLimit
Definition: TrainUnit.h:321
TRailGraphics::CodeI
Graphics::TBitmap * CodeI
Definition: GraphicUnit.h:1008
TTrainController::TotEarlyArrMins
float TotEarlyArrMins
values for performance file summary
Definition: TrainUnit.h:833
TTrain::WriteTrainToImage
void WriteTrainToImage(int Caller, Graphics::TBitmap *Bitmap)
Called by TTrainController::WriteTrainsToImage (called by TInterface::SaveOperatingImage1Click) to ad...
Definition: TrainUnit.cpp:9178
TRailGraphics::CodeL
Graphics::TBitmap * CodeL
Definition: GraphicUnit.h:1011
TTrainDataEntry::MaxBrakeRate
double MaxBrakeRate
< true if a description is given for the train, if only headcode given for a follow-on service then f...
Definition: TrainUnit.h:213
FNSNonRepeatToShuttle
@ FNSNonRepeatToShuttle
Definition: TrainUnit.h:66
TDisplay::PlotOutput
void PlotOutput(int Caller, int HPos, int VPos, Graphics::TBitmap *PlotItem)
Plot the graphic at screen position HPos & VPos.
Definition: DisplayUnit.cpp:86
TUtilities::SaveFileBool
void SaveFileBool(std::ofstream &OutFile, bool SaveBool)
stores '1' if the bool is true or '0' if false to the file, then a CR
Definition: Utilities.cpp:108
TTrainDataEntry::SignallerSpeed
int SignallerSpeed
in km/h for use when under signaller control
Definition: TrainUnit.h:223
TTrain::AbleToMove
bool AbleToMove(int Caller)
Indicates that a train is not prevented from moving - used to allow appropriate popup menu options wh...
Definition: TrainUnit.cpp:6921
TTrainController::MTBFHours
double MTBFHours
Mean time between train failures in timetable clock hours.
Definition: TrainUnit.h:825
TTrack::TInfrastructureFailureEntry::TVPos
int TVPos
Definition: TrackUnit.h:715
TOneRoute::ForceCancelRoute
void ForceCancelRoute(int Caller)
Cancel a route immediately if a train occupies it when travelling in the wrong direction (or occupies...
Definition: TrackUnit.cpp:19079
TTrain::TrainMode
TTrainMode TrainMode
mode of operation - either Timetable (running under timetable control) or Signaller (running under si...
Definition: TrainUnit.h:481
TTrainController::CheckNonRepeatingShuttleLinkTime
bool CheckNonRepeatingShuttleLinkTime(int Caller, TDateTime ReverseEventTime, TDateTime ForwardEventTime, int RepeatMins, int RepeatNumber)
The forward train is the finish shuttle entry 'Fns-sh', the reverse (new non-repeating service) time ...
Definition: TrainUnit.cpp:16328
FailNoPowerUnableToDepart
@ FailNoPowerUnableToDepart
Definition: TrainUnit.h:43
TUtilities::Format96HHMM
AnsiString Format96HHMM(TDateTime DateTime)
formats a TDateTime into an AnsiString of the form hh:mm where hh runs from 00 to 95 & resets when it...
Definition: Utilities.cpp:807
TTrain::CallOnMaxSpeed
static const int CallOnMaxSpeed
km/h
Definition: TrainUnit.h:313
TTrain::CheckAndCancelRouteForWrongEndEntry
void CheckAndCancelRouteForWrongEndEntry(int Caller, int Element, int EntryPos)
Checks whether Element and EntryPos (where train is about to enter) is on an existing route (or cross...
Definition: TrainUnit.cpp:3492
TRailGraphics::CodeW
Graphics::TBitmap * CodeW
Definition: GraphicUnit.h:1022
TTrain::ReleaseTime
TDateTime ReleaseTime
Definition: TrainUnit.h:475
TTrain::GetTrainHeadCode
AnsiString GetTrainHeadCode(int Caller)
Returns the train headcode, taking account of the RepeatNumber.
Definition: TrainUnit.cpp:5300
TTrain::LowEntryValue
bool LowEntryValue(int EntryLink) const
Returns true if EntryLink is 1, 2, 4 or 7, in these circumstances the front of the train (i....
Definition: TrainUnit.cpp:2805
RearSplit
@ RearSplit
Definition: TrainUnit.h:51
TActionVectorEntry::FrontStartOrRepeatDigits
int FrontStartOrRepeatDigits
dual-purpose variables used for the TrackVectorPositions of the rear and front train starting element...
Definition: TrainUnit.h:131
SignallerControlStop
@ SignallerControlStop
Definition: TrainUnit.h:53
TTrack::TrackVector
TTrackVector TrackVector
Definition: TrackUnit.h:828
TTrain::HasTrainGone
bool HasTrainGone()
Check whether the train has left the railway, so that it can be removed from the display at the next ...
Definition: TrainUnit.h:678
TRailGraphics::Code_i
Graphics::TBitmap * Code_i
Definition: GraphicUnit.h:972
TTrainController::ExcessLCDownMins
float ExcessLCDownMins
total excess time in minutes over the 3 minutes barriers down allowance for level crossings
Definition: TrainUnit.h:831
TRailGraphics::Code5
Graphics::TBitmap * Code5
Definition: GraphicUnit.h:995
TTrackElement::CallingOnSet
bool CallingOnSet
Used for for signals only when a train is being called on - used to plot the position lights.
Definition: TrackUnit.h:135
TUtilities::SaveFileInt
void SaveFileInt(std::ofstream &OutFile, int SaveInt)
stores the int value to the file, then a CR
Definition: Utilities.cpp:121
TrainController
TTrainController * TrainController
the object pointer, one object only - created in InterfaceUnit
Definition: TrainUnit.cpp:55
TUtilities::FailureMode
TFailureMode FailureMode
specifies whether no failures or minor, moderate or major random failures are to be applied (added at...
Definition: Utilities.h:119
TTrain::BufferAtExit
bool BufferAtExit(int Caller, int Element, int Exitpos) const
True if Element is a buffer and Exitpos is the buffer end.
Definition: TrainUnit.cpp:3216
TTrain::IsTrainTerminating
bool IsTrainTerminating(int Caller)
True if train service terminates at its current location.
Definition: TrainUnit.cpp:6893
TTrainController::SecondPassMessage
void SecondPassMessage(bool GiveMessages, AnsiString Message)
Give a user message during timetable integrity checking if GiveMessages is true, ignore if false.
Definition: TrainUnit.cpp:16426
SignallerStepForward
@ SignallerStepForward
Definition: TrainUnit.h:53
TTrack::GetHLocMin
int GetHLocMin()
Definition: TrackUnit.h:894
TDisplay::GetOutputLog5
TLabel * GetOutputLog5()
Definition: DisplayUnit.h:165
TTrainController::LateExits
int LateExits
Definition: TrainUnit.h:852
TTrainController::OpTimeToActUpdateCounter
unsigned int OpTimeToActUpdateCounter
<List of all ServiceRefs that have two or more same locations without a cdt between - loaded during S...
Definition: TrainUnit.h:876
TTrain::BeingCalledOn
bool BeingCalledOn
in course of being called on to a station
Definition: TrainUnit.h:387
TUtilities::CheckFileInt
bool CheckFileInt(std::ifstream &InFile, int Lowest, int Highest)
checks that the value is an int lying between Lowest & Highest (inclusive), returns true for success
Definition: Utilities.cpp:238
TRailGraphics::ChangeForegroundColour2
void ChangeForegroundColour2(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor NewForegroundColour, TColor BackgroundColour)
New function to do the same as the above but with fewer pixel changes - for use in LoadSession to avo...
Definition: GraphicUnit.cpp:3543
TRailGraphics::Code_e
Graphics::TBitmap * Code_e
Definition: GraphicUnit.h:968
TTrain::CalcTimeToAct
float CalcTimeToAct(int Caller, float &TimeToExit, THVShortPair &ExitPair)
new v2.2.0 for operator action panel. Calculates the time left for operator action to avoid unnecessa...
Definition: TrainUnit.cpp:9242
TUtilities::SignalChangeEventsPerFailure
int SignalChangeEventsPerFailure
number of signal changes between failures - reciprocal of failure probability per change
Definition: Utilities.h:95
TTrain::HoldAtLocationInTTMode
bool HoldAtLocationInTTMode
true if actions are needed before train departs
Definition: TrainUnit.h:337
TTrack::TInfrastructureFailureEntry
Definition: TrackUnit.h:714
TTrain::PlotTrainInZoomOutMode
void PlotTrainInZoomOutMode(int Caller, bool Flash)
Plots the train on screen in zoomed-out mode, state of 'Flash' determines whether the flashing trains...
Definition: TrainUnit.cpp:8988
TTrain::LoadOneSessionTrain
void LoadOneSessionTrain(int Caller, std::ifstream &InFile)
Create one train with relevant member values from the sesion file.
Definition: TrainUnit.cpp:8267
TTrain::PlotBackgroundGraphic
void PlotBackgroundGraphic(int Caller, int ArrayNumber, TDisplay *Disp) const
Replot the graphic pointed to by BackgroundPtr (see above) after a train has passed.
Definition: TrainUnit.cpp:3208
TTrainController::TotLateArrMins
float TotLateArrMins
Definition: TrainUnit.h:837
TTrain::HOffset
int HOffset[4]
Definition: TrainUnit.h:498
FailMissedArrival
@ FailMissedArrival
Definition: TrainUnit.h:40
TTrainController::TrainDataVectorCopy
TTrainDataVector TrainDataVectorCopy
vector containing the internal timetable, the copy is used for conflict analysis only
Definition: TrainUnit.h:890
TTrain::UpdateCounter
unsigned int UpdateCounter
used in train splitting operations to prevent too frequent checks for a location being long enough fo...
Definition: TrainUnit.h:471
NewService
@ NewService
Definition: TrainUnit.h:51
FailEnterLockedRoute
@ FailEnterLockedRoute
Definition: TrainUnit.h:41
TTrainController::PlotAllTrainsInZoomOutMode
void PlotAllTrainsInZoomOutMode(int Caller, bool Flash)
Plots all trains on screen in zoomed-out mode, state of 'Flash' determines whether the flashing train...
Definition: TrainUnit.cpp:17221
TOneRoute
A descendent of TOnePrefDir used for routes. Used during contruction of a route (ConstructRoute) and ...
Definition: TrackUnit.h:1518
TTrain::SignallerStopped
bool SignallerStopped
Definition: TrainUnit.h:493
TTrain::BufferZoomOutFlashRequired
bool BufferZoomOutFlashRequired
set when train is at buffers and is to flash in zoomout mode (i.e. when reaches buffers unexpectedly ...
Definition: TrainUnit.h:389
TAllRoutes::TRouteType
TRouteType
Definition: TrackUnit.h:1670
TTrain::TrainDataEntryPtr
TTrainDataEntry * TrainDataEntryPtr
points to the current position in the timetable's TrainDataVector
Definition: TrainUnit.h:377
TTrainDataEntry::StartSpeed
int StartSpeed
in km/h
Definition: TrainUnit.h:225
TTrainDataEntry::NumberOfTrains
int NumberOfTrains
number of repeats + 1
Definition: TrainUnit.h:221
TTrainController::Derailments
int Derailments
Definition: TrainUnit.h:844
clStationStopBackground
#define clStationStopBackground
Definition: GraphicUnit.h:302
TUtilities::CumulativeDelayedRandMinsAllTrains
int CumulativeDelayedRandMinsAllTrains
the running total of all random delays including knock-on delays for all trains, used to reduce total...
Definition: Utilities.h:101
TDisplay::GetOutputLog6
TLabel * GetOutputLog6()
Definition: DisplayUnit.h:170
ShuttleLink
@ ShuttleLink
Definition: TrainUnit.h:82
TRailGraphics::CodeM
Graphics::TBitmap * CodeM
Definition: GraphicUnit.h:1012
Crossover
@ Crossover
Definition: TrackUnit.h:66
TTrain::ClearToNextSignal
bool ClearToNextSignal(int Caller)
Checks forward from train LeadElement, following leading point attributes but ignoring trailing point...
Definition: TrainUnit.cpp:4900
AtLocation
@ AtLocation
Definition: TrainUnit.h:72
Signal
@ Signal
Definition: TrackUnit.h:76
TRailGraphics::Code_k
Graphics::TBitmap * Code_k
Definition: GraphicUnit.h:974
TRailGraphics::CodeF
Graphics::TBitmap * CodeF
Definition: GraphicUnit.h:1005
TTrack::ContinuationNameMap
std::map< AnsiString, char > ContinuationNameMap
map of all continuation names, char is a dummy
Definition: TrackUnit.h:796
TAllRoutes::GetRouteTypeAndNumber
TRouteType GetRouteTypeAndNumber(int Caller, int TrackVectorPosition, int LinkPos, int &RouteNumber)
Examines Route2MultiMap and if the element at TrackVectorPosition with LinkPos (can be entry or exit)...
Definition: TrackUnit.cpp:19735
TTrainController::TrainDataVector
TTrainDataVector TrainDataVector
Definition: TrainUnit.h:890
TTrainController::ConsolidateSARNTAtLoc
AnsiString ConsolidateSARNTAtLoc(int Caller, const AnsiString Input, int &NumTrainsAtLoc)
Removes duplicates from and sorts ServiceAndRepeatNumTotal into alphabetical order for AtLoc listing ...
Definition: TrainUnit.cpp:20597
TAllRoutes::NotAutoSigsRoute
@ NotAutoSigsRoute
Definition: TrackUnit.h:1671
TTrain::ExitTimeHalf
TDateTime ExitTimeHalf
Definition: TrainUnit.h:473
Exited
@ Exited
Definition: TrainUnit.h:88
TTrack::TrackElementAt
TTrackElement & TrackElementAt(int Caller, int At)
A range-checked version of TrackVector.at(At)
Definition: TrackUnit.cpp:10941
TTrain::ActionsSkippedFlag
bool ActionsSkippedFlag
prevents any further skipping until after the next departure
Definition: TrainUnit.h:335
TTrainController::LoadSessionTrains
void LoadSessionTrains(int Caller, std::ifstream &SessionFile)
load trains from a session file
Definition: TrainUnit.cpp:16919
TTrain::CheckOneSessionTrain
static bool CheckOneSessionTrain(std::ifstream &InFile)
Carries out an integrity check for the train section of a session file, if fails a message is given a...
Definition: TrainUnit.cpp:8554
TAllRoutes::GetRouteTypeAndGraphics
TRouteType GetRouteTypeAndGraphics(int Caller, int TrackVectorPosition, int LinkPos, Graphics::TBitmap *&EXGraphicPtr, Graphics::TBitmap *&EntryDirectionGraphicPtr)
Examines Route2MultiMap for the element at TrackVectorPosition with LinkPos (can be entry or exit).
Definition: TrackUnit.cpp:19561
TRailGraphics::CodeY
Graphics::TBitmap * CodeY
Definition: GraphicUnit.h:1024
TTrain::SetOneGraphicCode
Graphics::TBitmap * SetOneGraphicCode(char CodeChar)
Return a pointer to the graphic corresponding to the character 'CodeVhar'.
Definition: TrainUnit.cpp:2423
TTrain::DerailPending
bool DerailPending
Definition: TrainUnit.h:493
TTrainMode
TTrainMode
indicates train operating mode, 'None' for not in use
Definition: TrainUnit.h:59
TAllRoutes::TCallonEntry
Used to store relevant values when a call-on found, ready for plotting an unrestricted route.
Definition: TrackUnit.h:1692
TRailGraphics::Code_a
Graphics::TBitmap * Code_a
Definition: GraphicUnit.h:964
TTrain::StoppedAtLocation
bool StoppedAtLocation
Definition: TrainUnit.h:493
TTrainController::TContinuationTrainExpectationMultiMapIterator
TContinuationTrainExpectationMultiMap::iterator TContinuationTrainExpectationMultiMapIterator
iterator for the multimap
Definition: TrainUnit.h:766
TTrain::MaximumMassLimit
static const int MaximumMassLimit
kg (i.e. 10,000 tonnes)
Definition: TrainUnit.h:315
TTrainController::StopTTClockFlag
bool StopTTClockFlag
when true the timetable clock is stopped, used for messages display and train popup menu display etc
Definition: TrainUnit.h:810
FrontSplit
@ FrontSplit
Definition: TrainUnit.h:51
TTrain::MaxExitSpeed
double MaxExitSpeed
the maximum speed that the train can exit the next element
Definition: TrainUnit.h:437
TTrainController::BFHigh
bool BFHigh
Definition: TrainUnit.h:822
TRailGraphics::Code4
Graphics::TBitmap * Code4
Definition: GraphicUnit.h:994
TRailGraphics::Code_g
Graphics::TBitmap * Code_g
Definition: GraphicUnit.h:970
TTrain::LagEntryPos
int LagEntryPos
Definition: TrainUnit.h:373
SNTShuttle
@ SNTShuttle
Definition: TrainUnit.h:66
TTrain::NewDelay
double NewDelay
an additional random delay at a location (added at v2.13.0)
Definition: TrainUnit.h:449
TRailGraphics::CodeG
Graphics::TBitmap * CodeG
Definition: GraphicUnit.h:1006
Leave
@ Leave
Definition: TrainUnit.h:51
TTrainController::UnexpectedExits
int UnexpectedExits
Definition: TrainUnit.h:863
TTrainController::TLocServiceTimes::AtLocTime
AnsiString AtLocTime
Definition: TrainUnit.h:778
TTrainController::ServiceReference
AnsiString ServiceReference
String used to display the offending service in timetable error messages.
Definition: TrainUnit.h:806
TTrain::FirstLaterStopRecoverableTime
float FirstLaterStopRecoverableTime
this used to deduct from RecoverableTime when arrive at a location for OperatorActionpanel (OperatorA...
Definition: TrainUnit.h:463
TTrack::FailedSignalsVector
TFailedElementVector FailedSignalsVector
Definition: TrackUnit.h:794
TTrain::LeavingUnderSigControlAtContinuation
bool LeavingUnderSigControlAtContinuation
set when the train has reached an exit continuation when under signaller control, used to prevent the...
Definition: TrainUnit.h:403
TTrain::PlotTrainGraphic
void PlotTrainGraphic(int Caller, int ArrayNumber, TDisplay *Disp)
Plot the train's headcode character corresponding to ArrayNumber.
Definition: TrainUnit.cpp:3189
TRailGraphics::Code_j
Graphics::TBitmap * Code_j
Definition: GraphicUnit.h:973
FNil
@ FNil
Definition: Utilities.h:43
NoFormat
@ NoFormat
Definition: TrainUnit.h:66
TTrainController::TwoOrMoreLocationsWarningGiven
bool TwoOrMoreLocationsWarningGiven
new at v2.6.0 to allow loops
Definition: TrainUnit.h:818
FailBufferCrash
@ FailBufferCrash
Definition: TrainUnit.h:42
WaitingForFJO
@ WaitingForFJO
Definition: TrainUnit.h:42
FailMissedExitRailway
@ FailMissedExitRailway
Definition: TrainUnit.h:41
TTrainController::ControllerGetNewServiceDepartureInfo
AnsiString ControllerGetNewServiceDepartureInfo(int Caller, TActionVectorIterator Ptr, int RptNum, TTrainDataEntry *TDEPtr, TTrainDataEntry *LinkedTrainDataPtr, int IncrementalMinutes, int IncrementalDigits, AnsiString RetStr)
Similar to TTrain::GetNewServiceDepartureInfo for use in ContinuationEntryFloatingTTString.
Definition: TrainUnit.cpp:10674
TimeTimeLoc
@ TimeTimeLoc
Definition: TrainUnit.h:66
TTrain::GetOffsetValues
void GetOffsetValues(int Caller, int &HOffset, int &VOffset, int Link) const
Sets HOffset & VOffset (see above) for a single headcode character depending on the Link value.
Definition: TrainUnit.cpp:2734
TTrain::TimeToExit
float TimeToExit
in minutes: new for multiplayer, -1 = > 60 mins
Definition: TrainUnit.h:461
TTrain::Derailed
bool Derailed
Definition: TrainUnit.h:493
TRailGraphics::ChangeSpecificColour
void ChangeSpecificColour(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor ColourToBeChanged, TColor NewColour)
Definition: GraphicUnit.cpp:3572
Enter
@ Enter
Definition: TrainUnit.h:51
TRailGraphics::bm93set
Graphics::TBitmap * bm93set
Definition: GraphicUnit.h:518
TTrack::GetTrackVectorPositionFromString
int GetTrackVectorPositionFromString(int Caller, AnsiString String, bool GiveMessages)
Takes the ElementID value (an AnsiString) (e.g. "8-13", "N43-N127", etc) and returns the correspondin...
Definition: TrackUnit.cpp:8070
TRailGraphics::Code_q
Graphics::TBitmap * Code_q
Definition: GraphicUnit.h:980
TTrainController::SaveSessionLockedRoutes
void SaveSessionLockedRoutes(int Caller, std::ofstream &SessionFile)
save locked routes to a session file
Definition: TrainUnit.cpp:16968
TTrain::OriginalPowerAtRail
double OriginalPowerAtRail
new at v2.4.0 to store value before a failure so it can be restored from here when repaired
Definition: TrainUnit.h:455
TTrainFormattedInformation
Contains all information for a single timetable entry for use in the formatted timetable.
Definition: TrainUnit.h:278
TTrainController::IsSNTEntryLocated
bool IsSNTEntryLocated(int Caller, const TTrainDataEntry &TDEntry, AnsiString &LocationName)
New trains introduced with 'Snt' may be at a timetabled location or elsewhere. This function checks a...
Definition: TrainUnit.cpp:15606
TActionVectorEntry::NewDescription
AnsiString NewDescription
Definition: TrainUnit.h:121
TTrainController::TContinuationTrainExpectationEntry::VectorPosition
int VectorPosition
TrackVectorPosition for the continuation element.
Definition: TrainUnit.h:758
TTrain::NewTrainService
void NewTrainService(int Caller, bool NoLogFlag)
Carry out the actions needed when a train forms a new service (code Fns)
Definition: TrainUnit.cpp:6336
TTrainController::TTrainController
TTrainController()
Constructor.
Definition: TrainUnit.cpp:9678
Timetable
@ Timetable
Definition: TrainUnit.h:60
TUtilities::SaveFileDouble
void SaveFileDouble(std::ofstream &OutFile, double SaveDouble)
converts the double value to a string (if double stored directly it is truncated to 6 digits) then st...
Definition: Utilities.cpp:127
TTrainController::GetRepeatHeadCode
AnsiString GetRepeatHeadCode(int Caller, AnsiString BaseHeadCode, int RepeatNumber, int IncDigits)
Return the service headcode for the repeat service.
Definition: TrainUnit.cpp:15991
StartSequence
@ StartSequence
Definition: TrainUnit.h:77
TTrain::AbleToMoveButForSignal
bool AbleToMoveButForSignal(int Caller)
Indicates that a train is only prevented from moving by a signal - used to allow appropriate popup me...
Definition: TrainUnit.cpp:6980
TTrainController::OpActionPanelVisible
bool OpActionPanelVisible
new v2.2.0 flag to prevent time to act functions when not visible
Definition: TrainUnit.h:816
TTrack::GetAnyElementOppositeLinkPos
int GetAnyElementOppositeLinkPos(int Caller, int TrackVectorPosition, int LinkPos, bool &Derail)
Return the opposite link position for the element at TrackVectorPosition with link position LinkPos,...
Definition: TrackUnit.cpp:11815
TTrack::GapFlashRedPosition
int GapFlashRedPosition
TrackVectorPosition of the gap element that is flashing green or red.
Definition: TrackUnit.h:786
TActionVectorEntry::NumberOfRepeats
int NumberOfRepeats
the number of repeating services
Definition: TrainUnit.h:129
TAllRoutes::GetFixedRouteAt
const TOneRoute & GetFixedRouteAt(int Caller, int At) const
Returns a constant reference to the route at AllRoutesVector position 'At', after performing range ch...
Definition: TrackUnit.cpp:19407
TUtilities::clTransparent
TColor clTransparent
the display background colour, can be white, black or dark blue
Definition: Utilities.h:115
TTrainController::SignalStopWarning
bool SignalStopWarning
Definition: TrainUnit.h:808
TPrefDirElement::GetELinkPos
int GetELinkPos() const
Returns the ELink array position.
Definition: TrackUnit.h:275
TTrainController::CalcOperatingAndNotStartedTrainLateness
void CalcOperatingAndNotStartedTrainLateness(int Caller)
calculates additional lateness values for trains that haven't reached their destinations yet
Definition: TrainUnit.cpp:21566
ShuttleLinkTypeForRepeatEntry
@ ShuttleLinkTypeForRepeatEntry
Definition: TrainUnit.h:82
TTrain::SignallerStopBrakeRate
double SignallerStopBrakeRate
the train brake rate when stopping under signaller control
Definition: TrainUnit.h:445
TTrainController::TLocServiceTimes::Location
AnsiString Location
Definition: TrainUnit.h:776
TOneCompleteFormattedTrain
A single train with its headcode + list of actions for use in the formatted timetable.
Definition: TrainUnit.h:265
TTrainController::SignallerTrainRemovedOnAutoSigsRoute
bool SignallerTrainRemovedOnAutoSigsRoute
true if train was on an AutoSigsRoute when removed by the signaller
Definition: TrainUnit.h:814
TTrack::InactiveTrackVector
TTrackVector InactiveTrackVector
Definition: TrackUnit.h:828
TTrain::LogAction
void LogAction(int Caller, AnsiString HeadCode, AnsiString OtherHeadCode, TActionType ActionType, AnsiString LocationName, AnsiString SplitDistribution, TDateTime TimetableNonRepeatTime, bool Warning)
Send a message to the performance log and performance file, and if the message is flagged as a warnin...
Definition: TrainUnit.cpp:5407
Terminate
@ Terminate
Definition: TrainUnit.h:51
TTrainController::MRSLow
bool MRSLow
Definition: TrainUnit.h:822
TTrack::ActiveTrackElementNameMap
TActiveTrackElementNameMap ActiveTrackElementNameMap
< map of coupled continuations
Definition: TrackUnit.h:800
TTrain::Crashed
bool Crashed
Definition: TrainUnit.h:493
TTrackElement::HLoc
int HLoc
Definition: TrackUnit.h:149
TTrain::HeadCodePosition
Graphics::TBitmap * HeadCodePosition[4]
Set from the HeadCodeGrPtr[4] pointer values, HeadCodePosition[0] is always the front,...
Definition: TrainUnit.h:509
TTrain::TrainID
int TrainID
the train's identification number
Definition: TrainUnit.h:375
Running
@ Running
Definition: TrainUnit.h:88
TAllFormattedTrains
std::vector< TTrainFormattedInformation > TAllFormattedTrains
vector of all timetabled trains for use in the formatted timetable
Definition: TrainUnit.h:287
TRailGraphics::ChangeBackgroundColour3
void ChangeBackgroundColour3(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor NewBackgroundColour, TColor OldBackgroundColour)
as above but uses Scanline
Definition: GraphicUnit.cpp:3669
TRailGraphics::smCyan
Graphics::TBitmap * smCyan
Definition: GraphicUnit.h:899
TTrainController::MovingSuccessor
bool MovingSuccessor(const TActionVectorEntry &AVEntry)
A shorthand function that returns true if the successor to a given timetable action command should be...
Definition: TrainUnit.cpp:14903
TTrainController::LogActionError
void LogActionError(int Caller, AnsiString HeadCode, AnsiString OtherHeadCode, TActionEventType ActionEventType, AnsiString LocationID)
Send an error message to the performance log and file, and as a warning if appropriate.
Definition: TrainUnit.cpp:16464
TTrainController::TrainVectorAt
TTrain & TrainVectorAt(int Caller, int VecPos)
Return a reference to the train at position VecPos in the TrainVector, carries out range checking on ...
Definition: TrainUnit.cpp:17244
TTrain::FrontElementSpeedLimit
int FrontElementSpeedLimit
Definition: TrainUnit.h:465
TTrack::NumberOfPlatforms
int NumberOfPlatforms(int Caller, AnsiString LocationName)
Returns the number of separate platforms (not platform elements) at a given location,...
Definition: TrackUnit.cpp:12035
TTrack::TSigElement::SpeedTag
int SpeedTag
the TrackElement SpeedTag value - specifies the signal element
Definition: TrackUnit.h:728
TTrain::DeleteTrain
void DeleteTrain(int Caller)
This is a housekeeping function to delete train heap objects (bitmaps) explicitly rather than by usin...
Definition: TrainUnit.cpp:243
TTrainController::NumFailures
int NumFailures
Definition: TrainUnit.h:864
TTrain::ExitSpeedFull
double ExitSpeedFull
speed when leaving the next element
Definition: TrainUnit.h:431
TTrain::SetHeadCodeGraphics
void SetHeadCodeGraphics(int Caller, AnsiString Code)
Set the four HeadCodeGrPtr[4] pointers to the appropriate character graphics with the current backgro...
Definition: TrainUnit.cpp:2620
TAllRoutes::SetTrailingSignalsOnAutoSigsRoute
void SetTrailingSignalsOnAutoSigsRoute(int Caller, int TrackVectorPosition, int XLinkPos)
Enter with signal at TrackVectorElement already set to red by the passing train.
Definition: TrackUnit.cpp:20418
clB5G5R5
#define clB5G5R5
Definition: GraphicUnit.h:287
TUtilities::TimeStamp
AnsiString TimeStamp()
creates a string of the form 'hh:mm:ss' for use in call & event logging
Definition: Utilities.cpp:73
TTrainController::RandomFailureCounter
unsigned int RandomFailureCounter
new at v2.4.0 for train failures, resets after 53 seconds (53 prime so can trigger at any clock time)
Definition: TrainUnit.h:880
TAllRoutes::TLockedRouteClass::RouteNumber
int RouteNumber
the vector position number of the relevant route in AllRoutesVector
Definition: TrackUnit.h:1657
TTrainController::AddTrain
bool AddTrain(int Caller, int RearPosition, int FrontPosition, AnsiString HeadCode, int StartSpeed, int Mass, double MaxRunningSpeed, double MaxBrakeRate, double PowerAtRail, AnsiString ModeStr, TTrainDataEntry *TrainDataEntryPtr, int RepeatNumber, int IncrementalMinutes, int IncrementalDigits, int SignallerSpeed, bool SignallerControl, TActionEventType &EventType)
Introduce a new train to the railway, with the characteristics specified, returns true for success,...
Definition: TrainUnit.cpp:10161
TTrainController::~TTrainController
~TTrainController()
Destructor.
Definition: TrainUnit.cpp:9731
TTrain::RepeatShuttleOrNewNonRepeatService
void RepeatShuttleOrNewNonRepeatService(int Caller, bool NoLogFlag)
Carry out the actions needed to create either a new shuttle service or (if all repeats have finished)...
Definition: TrainUnit.cpp:6805
TTrainController::TwoLocationList
TServiceCallingLocsList TwoLocationList
Definition: TrainUnit.h:874
TTrain::GetNewServiceDepartureInfo
AnsiString GetNewServiceDepartureInfo(int Caller, TActionVectorEntry *Ptr, int RptNum, TTrainDataEntry *LinkedTrainDataPtr, AnsiString RetStr, bool TimetableTime)
called during FloatingLabelNextString to find the next service departure time & next location (last b...
Definition: TrainUnit.cpp:7604
TTrain::Description
AnsiString Description
needs own HeadCode & description because repeat entries will differ from TrainDataEntry values (Descr...
Definition: TrainUnit.h:329
TUtilities::CheckAndReadFileInt
bool CheckAndReadFileInt(std::ifstream &InFile, int Lowest, int Highest, int &OutInt)
checks that the value is an int lying between Lowest & Highest (inclusive), returns true for success ...
Definition: Utilities.cpp:280
TRailGraphics::Code7
Graphics::TBitmap * Code7
Definition: GraphicUnit.h:997
TTrackElement::ActiveTrackElementName
AnsiString ActiveTrackElementName
Location name used either in the timetable or for a continuation (continuation names not used in time...
Definition: TrackUnit.h:128
TRailGraphics::bm94set
Graphics::TBitmap * bm94set
Definition: GraphicUnit.h:520
TUtilities::DelayMode
TDelayMode DelayMode
specifies whether no delays or minor, moderate or major random delays are to be applied (added at v2....
Definition: Utilities.h:117
TTrainController::LogEvent
void LogEvent(AnsiString Str)
store Str to the event log - moved from TUtilities for v0.6 so can record the tt clock value
Definition: TrainUnit.cpp:9742
TRailGraphics::CodeO
Graphics::TBitmap * CodeO
Definition: GraphicUnit.h:1014
TTrain::ExitPair
THVShortPair ExitPair
H & V coordinates of the exit element related to TimeToExit, new for multiplayer.
Definition: TrainUnit.h:479
TAllRoutes::SignallerRemovedTrainAutoRoute
TOneRoute SignallerRemovedTrainAutoRoute
if train was on an AutoSigsRoute when removed then this stores the route so that signals can be reset
Definition: TrackUnit.h:1744
TTrainController::MassHigh
bool MassHigh
Definition: TrainUnit.h:822
TTrainController::CheckAndPopulateListOfIDs
bool CheckAndPopulateListOfIDs(int Caller, AnsiString IDSet, TNumList &ExitList, bool GiveMessages)
Used to compile ExitList from a string list of element IDs, returns true for success or gives a messa...
Definition: TrainUnit.cpp:12345
TTrain::PlotTrainWithNewBackgroundColour
void PlotTrainWithNewBackgroundColour(int Caller, TColor NewBackgroundColour, TDisplay *Disp)
Changes the train's background colour (e.g. to pale green if stopped at a station) Note that this use...
Definition: TrainUnit.cpp:3688
TUtilities::SaveFileString
void SaveFileString(std::ofstream &OutFile, AnsiString SaveString)
stores the string value to the file, then a '0' delimiter then a CR
Definition: Utilities.cpp:135
TTrain::PlotAlternativeTrackRouteGraphic
void PlotAlternativeTrackRouteGraphic(int Caller, unsigned int LagElement, int LagELinkPos, int HOffset, int VOffset, TStraddle StraddleValue)
When a train moves off a bridge the other track may contain a route or have a train on it that has be...
Definition: TrainUnit.cpp:3388
TTrain::ExitTimeFull
TDateTime ExitTimeFull
times used in SetTrainMovementValues corresponding to the next element the train runs on
Definition: TrainUnit.h:473
TTrainController::CalcDistanceToRedSignalandStopTime
int CalcDistanceToRedSignalandStopTime(int Caller, int TrackVectorPosition, int TrackVectorPositionEntryPos, bool SigControlAndCanPassRedSignal, TActionVectorEntry *AVPtr, AnsiString HeadCode, int TrainID, float &CurrentStopTime, float &LaterStopTime, float &RecoverableTime, int &AvTrackSpeed, int &DistanceToExit, THVShortPair &ExitPair)
new v2.2.0 (DistanceToExit added for multiplayer), calcs distances to red signal & exit,...
Definition: TrainUnit.cpp:21879
TTrack::GapFlashRed
TGraphicElement * GapFlashRed
the red & green circle graphics used to show where the gaps are
Definition: TrackUnit.h:808
LocTypeForRepeatEntry
@ LocTypeForRepeatEntry
Definition: TrainUnit.h:72
SignallerChangeDirection
@ SignallerChangeDirection
Definition: TrainUnit.h:53
TAllRoutes::TLockedRouteClass
Handles routes that are locked because of approaching trains.
Definition: TrackUnit.h:1655
TTrain::TrainAtLocation
bool TrainAtLocation(int Caller, AnsiString &LocationName)
True when the train is stopped at a timetabled location.
Definition: TrainUnit.cpp:9135
TTrain::IsTrainIDOnBridgeTrackPos01
bool IsTrainIDOnBridgeTrackPos01(int Caller, unsigned int TrackVectorPosition)
True if train is on a bridge on trackpos 0 & 1.
Definition: TrainUnit.cpp:3252
TActionVectorEntry
Contains a single train action in a timetable - repeat entry is also of this class though no train ac...
Definition: TrainUnit.h:119
TTrainController::TotLateExitMins
float TotLateExitMins
Definition: TrainUnit.h:840
TTrainController::CheckSessionContinuationAutoSigEntries
bool CheckSessionContinuationAutoSigEntries(int Caller, std::ifstream &SessionFile)
Part of the session file integrity check for ContinuationAutoSigEntries, true for success.
Definition: TrainUnit.cpp:17090
TOneCompleteFormattedTrain::OneFormattedTrainVector
TOneFormattedTrainVector OneFormattedTrainVector
Definition: TrainUnit.h:268
TRailGraphics::Code1
Graphics::TBitmap * Code1
Definition: GraphicUnit.h:991
TTrainController::TimetableMessage
void TimetableMessage(bool GiveMessages, AnsiString Message)
Sends a message to the user if GiveMessages is true, including ServiceReference (see above) if not nu...
Definition: TrainUnit.cpp:16405
TDisplay
Definition: DisplayUnit.h:49
TTrackElement::ConnLinkPos
int ConnLinkPos[4]
Connecting element link position (i.e. array positions of the connecting element links,...
Definition: TrackUnit.h:147
TTrainController::ContinuationAutoSigVector
TContinuationAutoSigVector ContinuationAutoSigVector
vector for TContinuationAutoSigEntry objects
Definition: TrainUnit.h:882
TTrain::StoppedWithoutPower
bool StoppedWithoutPower
Definition: TrainUnit.h:494
TTrain::NameInTimetableBeforeCDT
int NameInTimetableBeforeCDT(int Caller, AnsiString Name, bool &Stop)
Returns the number by which the train ActionVectorEntryPtr needs to be incremented to point to the lo...
Definition: TrainUnit.cpp:4837
TimeCmdDescription
@ TimeCmdDescription
Definition: TrainUnit.h:67
TTrainFormattedInformation::Header
AnsiString Header
description, mass, power, brake rate etc
Definition: TrainUnit.h:280
TActionEventType
TActionEventType
Used for reporting error conditions & warnings.
Definition: TrainUnit.h:38
TTrainController::TrainExistsAtIdent
bool TrainExistsAtIdent(int Caller, int TrainID)
new at v2.4.0 return true if find the train (added at v2.4.0 as can select a removed train in Actions...
Definition: TrainUnit.cpp:10495
SignallerJoin
@ SignallerJoin
Definition: TrainUnit.h:52
TTrain::RearStartElement
int RearStartElement
start TrackVectorPosition element for rear of train
Definition: TrainUnit.h:363
TTrain::StepForwardFlag
bool StepForwardFlag
set when the signaller command to step forward one element has been given
Definition: TrainUnit.h:413
TTrainController::SplitTrainInfo
bool SplitTrainInfo(int Caller, AnsiString TrainInfoStr, AnsiString &HeadCode, AnsiString &Description, int &StartSpeed, int &MaxRunningSpeed, int &Mass, double &MaxBrakeRate, double &PowerAtRail, int &SignallerSpeed, bool GiveMessages)
Parse a train information entry, return true for success; PowerAtRail changed to double& from int& at...
Definition: TrainUnit.cpp:12445
TTrainController::TotEarlyExitMins
float TotEarlyExitMins
Definition: TrainUnit.h:836
TFixedTrackPiece::Config
TConfiguration Config[4]
the type of link - see TConfiguration above
Definition: TrackUnit.h:97
TTrainController::Operate
void Operate(int Caller)
called every clock tick to introduce new trains and update existing trains
Definition: TrainUnit.cpp:9756
TTrackElement::Failed
bool Failed
New parameter added at v2.13.0 for failed points, signals & TSRs.
Definition: TrackUnit.h:141
TTrackElement::TrainIDOnElement
int TrainIDOnElement
Definition: TrackUnit.h:155
TTrainController::SkippedTTEvents
int SkippedTTEvents
Definition: TrainUnit.h:859
TRailGraphics::smRed
Graphics::TBitmap * smRed
Definition: GraphicUnit.h:906
TTrain::PowerAtRail
double PowerAtRail
in Watts (taken as 80% of the train's Gross Power, i.e. that entered by the user)
Definition: TrainUnit.h:453
TRailGraphics::LCPlainMan
Graphics::TBitmap * LCPlainMan
Definition: GraphicUnit.h:750
TDisplay::GetOutputLog4
TLabel * GetOutputLog4()
Definition: DisplayUnit.h:160
TAllRoutes::RebuildRailwayFlag
bool RebuildRailwayFlag
this is set whenever a route has to be cancelled forcibly in order to force a ClearandRebuildRailway ...
Definition: TrackUnit.h:1725
TTrainDataEntry::MaxRunningSpeed
double MaxRunningSpeed
in km/h
Definition: TrainUnit.h:215
TUtilities::LoadFileInt
int LoadFileInt(std::ifstream &InFile)
loads an int value from the file
Definition: Utilities.cpp:162
TTrackElement::ElementID
AnsiString ElementID
the element identifier based on position in the railway
Definition: TrackUnit.h:130
TUtilities::MinorDelayCutoff
float MinorDelayCutoff
Definition: Utilities.h:50
TTimeToExitMultiMapEntry
std::pair< THVShortPair, TExitInfo > TTimeToExitMultiMapEntry
Definition: TrainUnit.h:113
TRailGraphics::Code6
Graphics::TBitmap * Code6
Definition: GraphicUnit.h:996
TTrainDataEntry::TrainOperatingDataVector
TTrainOperatingDataVector TrainOperatingDataVector
operating information for the train including all its repeats
Definition: TrainUnit.h:229
TTrain::DelayedRandMins
double DelayedRandMins
the remaining random delay at any point in time for the train (added at v2.13.0)
Definition: TrainUnit.h:447
TTrackElement::VLoc
int VLoc
The h & v locations in the railway (top lh corner of the first build screen = 0,0)
Definition: TrackUnit.h:149
TTrainController::OperatingTrainLateMins
float OperatingTrainLateMins
total late minutes of operating trains on exit operation for locations not reached yet
Definition: TrainUnit.h:829
TTrack::GetNonPointsOppositeLinkPos
int GetNonPointsOppositeLinkPos(int LinkPosIn)
Return the corresponding link position (track always occupies either links 0 & 1 or 2 & 3)
Definition: TrackUnit.h:910
TTrain::UnplotTrain
void UnplotTrain(int Caller)
Unplot train from screen in zoomed-in mode.
Definition: TrainUnit.cpp:562
TTrain::IsThereAnAdjacentTrain
bool IsThereAnAdjacentTrain(int Caller, TTrain *&TrainToBeJoinedBy)
Definition: TrainUnit.cpp:5323
TTrain::TRSTime
TDateTime TRSTime
Definition: TrainUnit.h:475
TAllRoutes::TLockedRouteClass::LastTrackVectorPosition
unsigned int LastTrackVectorPosition
the TrackVector position of the last (i.e. most forward) element in the route (this will be truncated...
Definition: TrackUnit.h:1661
FailMissedJBO
@ FailMissedJBO
Definition: TrainUnit.h:40
TUtilities::EventLog
std::deque< AnsiString > EventLog
event store, saved to the errorlog for diagnostic purposes
Definition: Utilities.h:107
TAllRoutes::NoRoute
@ NoRoute
Definition: TrackUnit.h:1671
TActionVectorEntry::FormatType
TTimetableFormatType FormatType
defines the timetable action type
Definition: TrainUnit.h:137
TTrain::RemainHereLogNotSent
bool RemainHereLogNotSent
flag to prevent repeated logs, new at v1.2.0
Definition: TrainUnit.h:341
TRailGraphics::CodeU
Graphics::TBitmap * CodeU
Definition: GraphicUnit.h:1020
TTrainController::RestartTime
TDateTime RestartTime
TTClockTime when operation pauses ( = timetable start time prior to operation) TTClockTime is calcula...
Definition: TrainUnit.h:721
Moderate
@ Moderate
Definition: Utilities.h:38
TTrainController::TimetableStartTime
TDateTime TimetableStartTime
the start time of the current timetable
Definition: TrainUnit.h:719
clSPADBackground
#define clSPADBackground
Definition: GraphicUnit.h:301
TTrainController::UnplotTrains
void UnplotTrains(int Caller)
unplot all trains from screen
Definition: TrainUnit.cpp:10145
TRailGraphics::smBrightGreen
Graphics::TBitmap * smBrightGreen
Definition: GraphicUnit.h:897
TTrainController::EarlyArrivals
int EarlyArrivals
Definition: TrainUnit.h:845
FailCreateTrain
@ FailCreateTrain
Definition: TrainUnit.h:39
TTrain::IncrementalDigits
int IncrementalDigits
the number of digits to increment by in repeat entries
Definition: TrainUnit.h:359
TTrainController::CreateFormattedTimetable
void CreateFormattedTimetable(int Caller, AnsiString RailwayTitle, AnsiString TimetableTitle, AnsiString CurDir)
Examines the internal timetable (TrainDataVector) and creates from it a chronological (....
Definition: TrainUnit.cpp:17257
TTrack::RouteFlashFlag
bool RouteFlashFlag
true while a route is flashing prior to being set
Definition: TrackUnit.h:770
TRailGraphics::Code_v
Graphics::TBitmap * Code_v
Definition: GraphicUnit.h:985
TTrackElement::StationEntryStopLinkPos1
int StationEntryStopLinkPos1
Definition: TrackUnit.h:153
Major
@ Major
Definition: Utilities.h:38
Points
@ Points
Definition: TrackUnit.h:66
TTrainController::SplitEntry
bool SplitEntry(int Caller, AnsiString OneEntry, bool GiveMessages, bool CheckLocationsExistInRailway, AnsiString &First, AnsiString &Second, AnsiString &Third, AnsiString &Fourth, int &RearStartOrRepeatMins, int &FrontStartPosition, TTimetableFormatType &TimetableFormatType, TTimetableLocationType &LocationType, TTimetableSequenceType &SequenceType, TTimetableShuttleLinkType &ShuttleLinkType, TNumList &ExitList, bool &Warning)
Parse a single timetable service action, return true for success.
Definition: TrainUnit.cpp:11850
TRailGraphics::LCPlain
Graphics::TBitmap * LCPlain
Definition: GraphicUnit.h:743
TTrain::LastActionDelayFlag
bool LastActionDelayFlag
used when trains join to ensure that there is a 30 second delay before the actual join takes place af...
Definition: TrainUnit.h:399
TTrainController::TotLatePassMins
float TotLatePassMins
Definition: TrainUnit.h:839
TTrainController::StripSpaces
void StripSpaces(int Caller, AnsiString &Input)
Strip both leading and trailing spaces at ends of Input and spaces before and after all commas and se...
Definition: TrainUnit.cpp:15505
FailMissedJoinOther
@ FailMissedJoinOther
Definition: TrainUnit.h:40
TTrain::JoinedOtherTrainFlag
bool JoinedOtherTrainFlag
true when the train has joined another train following an 'Fjo' timetable command or a signaller join...
Definition: TrainUnit.h:397
TTrainController::BFLow
bool BFLow
Definition: TrainUnit.h:822
TTrainController::TotArrDepPass
int TotArrDepPass
Definition: TrainUnit.h:862
SignallerPassRedSignal
@ SignallerPassRedSignal
Definition: TrainUnit.h:53
TRailGraphics::ChangeForegroundColour
void ChangeForegroundColour(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor NewForegroundColour, TColor BackgroundColour)
Definition: GraphicUnit.cpp:3517
TRailGraphics::CodeE
Graphics::TBitmap * CodeE
Definition: GraphicUnit.h:1004
FailLockedRoute
@ FailLockedRoute
Definition: TrainUnit.h:39
TTrain::SignallerRemoved
bool SignallerRemoved
set when removed under signaller control to force a removal from the display at the next clock tick
Definition: TrainUnit.h:407
clB5G3R0
#define clB5G3R0
Definition: GraphicUnit.h:268
TTrain::FrontCodePtr
Graphics::TBitmap * FrontCodePtr
points to the front headcode segment, this is set to red or blue depending on TrainMode
Definition: TrainUnit.h:513
TOneRoute::SetRouteSignals
void SetRouteSignals(int Caller) const
Called when setting a route to set all signals appropriately. Also called when a new train is added a...
Definition: TrackUnit.cpp:18039
Continuation
@ Continuation
Definition: TrackUnit.h:66
TTrainController::CallOnWarning
bool CallOnWarning
Definition: TrainUnit.h:808
TTrain::ActualArrivalTime
TDateTime ActualArrivalTime
location departure time and 'train ready to start' time (TRSTime is 10 seconds before the ReleaseTime...
Definition: TrainUnit.h:475
TTrack::GetFilletGraphic
Graphics::TBitmap * GetFilletGraphic(int Caller, TTrackElement TrackElement)
Return a pointer to the point fillet (the bit that appears to move when points are changed) for the p...
Definition: TrackUnit.cpp:7828
GraphicUnit.h
PerfLogUnit.h
TUtilities::MajorDelayFactor
float MajorDelayFactor
Definition: Utilities.h:55
TTrack::PointFlashFlag
bool PointFlashFlag
< true if a route set through an LC that is closed to trains (& therefore needs to be opened)
Definition: TrackUnit.h:768
TTrack::ThisLocationLongEnoughForSplit
bool ThisLocationLongEnoughForSplit(int Caller, AnsiString HeadCode, int TrainID, AnsiString LocationName, int LeadElement, int LeadExitPos, int MidElement, int MidEntryPos, int &FrontTrainFrontPos, int &FrontTrainRearPos, int &RearTrainFrontPos, int &RearTrainRearPos, bool &TemporaryDelay)
checks if the track that the train is on is long enough for a split, returns false if not,...
Definition: TrackUnit.cpp:11261
TTrainController::OnTimeDeps
int OnTimeDeps
Definition: TrainUnit.h:855
TTrain::RestoreTimetableLocation
AnsiString RestoreTimetableLocation
stores the location name at which signaller control is taken, to ensure that it is back at that locat...
Definition: TrainUnit.h:485
TTrainController::TimetableIntegrityCheck
bool TimetableIntegrityCheck(int Caller, char *FileName, bool GiveMessages, bool CheckLocationsExistInRailway)
Checks overall timetable integrity, calls many other specific checking functions, returns true for su...
Definition: TrainUnit.cpp:10913
AllRoutes
TAllRoutes * AllRoutes
the object pointer, object created in InterfaceUnit
Definition: TrackUnit.cpp:55
TTrain::SignallerMaxSpeed
int SignallerMaxSpeed
maximum train speed under signaller control (in km/h)
Definition: TrainUnit.h:369
NoEvent
@ NoEvent
Definition: TrainUnit.h:39
FailSplitDueToOtherTrain
@ FailSplitDueToOtherTrain
Definition: TrainUnit.h:39
TTrainController::SendPerformanceSummary
void SendPerformanceSummary(int Caller, std::ofstream &PerfFile)
At the end of operation a summary of overall performance is sent to the performance file by this func...
Definition: TrainUnit.cpp:21070
TTrack::TInfrastructureFailureEntry::FailureTime
TDateTime FailureTime
Definition: TrackUnit.h:716
TTrain::ResetTrainElementID
void ResetTrainElementID(int Caller, unsigned int TrackVectorPosition, int EntryPos)
After a train has moved off an element that element has its TrainIDOnElement value set back to -1 to ...
Definition: TrainUnit.cpp:3346
TRailGraphics::smMagenta
Graphics::TBitmap * smMagenta
Definition: GraphicUnit.h:902
RepairFailedTrain
@ RepairFailedTrain
Definition: TrainUnit.h:53
TTrainController::AllServiceCallingLocsMap
TAllServiceCallingLocsMap AllServiceCallingLocsMap
Definition: TrainUnit.h:789
TRailGraphics::smPaleGreen
Graphics::TBitmap * smPaleGreen
Definition: GraphicUnit.h:905
TRailGraphics::CodeS
Graphics::TBitmap * CodeS
Definition: GraphicUnit.h:1018
TTrainController::ContinuationEntryFloatingTTString
AnsiString ContinuationEntryFloatingTTString(int Caller, TTrainDataEntry *TTDEPtr, int RepeatNumber, int IncrementalMinutes, int IncrementalDigits)
Build string for use in floating window for expected trains at continuations.
Definition: TrainUnit.cpp:10526
TFixedTrackPiece::SpeedTag
int SpeedTag
The element identification number - corresponds to the relevant SpeedButton->Tag.
Definition: TrackUnit.h:88
TTrain::FollowOnServiceRef
AnsiString FollowOnServiceRef
Definition: TrainUnit.h:331
TTrain::SPADFlag
bool SPADFlag
set when running past a red signal without permission flags to indicate relevant stop conditions or p...
Definition: TrainUnit.h:491
TTrain::Stopped
bool Stopped()
True if the train has stopped for any reason.
Definition: TrainUnit.h:688
Minor
@ Minor
Definition: Utilities.h:38
TTrack::SigTableGroundSignal
TSigElement SigTableGroundSignal[40]
new at version 0.6 for ground signals
Definition: TrackUnit.h:742
TTrain::TrainToJoinIsAdjacent
bool TrainToJoinIsAdjacent(int Caller, TTrain *&TrainToJoin)
True for a train waiting to join another when the other train is adjacent.
Definition: TrainUnit.cpp:6620
TActionVectorEntry::ExitList
TNumList ExitList
the list of valid train exit TrackVector positions for 'Fer' entries (empty to begin with)
Definition: TrainUnit.h:135
TTrainController::OnTimeExits
int OnTimeExits
Definition: TrainUnit.h:857
TTrain::OldZoomOutElement
int OldZoomOutElement[3]
stores the Lead, Mid & Lag TrackVectorPositions, used for unplotting trains from the old position in ...
Definition: TrainUnit.h:500
TPrefDirElement::GetELink
int GetELink() const
Returns ELink.
Definition: TrackUnit.h:269
TDisplay::GetOutputLog8
TLabel * GetOutputLog8()
Definition: DisplayUnit.h:180
WaitingForJBO
@ WaitingForJBO
Definition: TrainUnit.h:42
TUtilities::CheckFileDouble
bool CheckFileDouble(std::ifstream &InFile)
checks that the value is a double, returns true for success
Definition: Utilities.cpp:331
TTrainDataEntry::ExplicitDescription
bool ExplicitDescription
< headcode is the first train's headcode, rest are calculated from repeat information; ServiceReferen...
Definition: TrainUnit.h:211
TTrain::Straddle
TStraddle Straddle
the current Straddle value of the train (see TStraddle above)
Definition: TrainUnit.h:520
SignallerStop
@ SignallerStop
Definition: TrainUnit.h:53
TRailGraphics::CodeB
Graphics::TBitmap * CodeB
Definition: GraphicUnit.h:1001
TAllRoutes::DiagonalFouledByRouteOrTrain
bool DiagonalFouledByRouteOrTrain(int Caller, int HLoc, int VLoc, int DiagonalLinkNumber)
The track geometry allows diagonals to cross without occupying the same track element,...
Definition: TrackUnit.cpp:21096
TTrain::RevisedStoppedAtLoc
bool RevisedStoppedAtLoc() const
Definition: TrainUnit.h:528
Display
TDisplay * Display
The object pointer for the on-screen display, object created in InterfaceUnit.
Definition: DisplayUnit.cpp:54
TTrack::GetVLocMin
int GetVLocMin()
Definition: TrackUnit.h:904
TRailGraphics::Code3
Graphics::TBitmap * Code3
Definition: GraphicUnit.h:993
TUtilities::CheckFileBool
bool CheckFileBool(std::ifstream &InFile)
checks that the value is a bool returns true for success
Definition: Utilities.cpp:209
TTrain::MaximumPowerLimit
static const int MaximumPowerLimit
Watts (i.e. 100MW)
Definition: TrainUnit.h:317
TTrain::PlotStartPosition
void PlotStartPosition(int Caller)
Plots the train and sets up all relevant members for a new train when it is introduced into the railw...
Definition: TrainUnit.cpp:289
TTrack::TActiveTrackElementNameMapEntry
std::pair< AnsiString, int > TActiveTrackElementNameMapEntry
Definition: TrackUnit.h:710
TTrainController::SingleServiceOutput
void SingleServiceOutput(int Caller, int SSVectorNumber, TNumList MarkerList, TTrainDataVector &SingleServiceVector, std::ofstream &VecFile)
Outputs the single service vector for train direction analysis purposes in timetable conflict analysi...
Definition: TrainUnit.cpp:20211
TTrack::TInfrastructureFailureEntry::RepairTime
TDateTime RepairTime
Definition: TrackUnit.h:717
TTrainController::TContinuationAutoSigEntry::SecondDelay
double SecondDelay
Definition: TrainUnit.h:730
TTrain::UnplotTrainInZoomOutMode
void UnplotTrainInZoomOutMode(int Caller)
Unplot train from screen in zoomed-out mode.
Definition: TrainUnit.cpp:9098
TTimetableFormatType
TTimetableFormatType
Timetable entry types.
Definition: TrainUnit.h:65
TTrainController::TrainFailedWarning
bool TrainFailedWarning
Flags to enable the relevant warning graphics to flash at the left hand side of the screen.
Definition: TrainUnit.h:808
TTrain::NextTrainID
static int NextTrainID
< km/h
Definition: TrainUnit.h:326
TTrainController::TLocServiceTimes
Class used for timetable conflict file compilation.
Definition: TrainUnit.h:775
TTrainOperatingData::RunningEntry
TRunningEntry RunningEntry
Definition: TrainUnit.h:187
NotAShuttleLink
@ NotAShuttleLink
Definition: TrainUnit.h:82
TRailGraphics::gl91set
Graphics::TBitmap * gl91set
Definition: GraphicUnit.h:726
TRailGraphics::Code_y
Graphics::TBitmap * Code_y
Definition: GraphicUnit.h:988
TAllRoutes::CheckMapAndRoutes
void CheckMapAndRoutes(int Caller)
Diagnostic function - checks equivalence for each route between entries in PrefDirVector and those in...
Definition: TrackUnit.cpp:20192
TAllRoutes::GetRouteElementDataFromRoute2MultiMap
TRouteElementPair GetRouteElementDataFromRoute2MultiMap(int Caller, int HLoc, int VLoc, TRouteElementPair &SecondPair)
Retrieve up to two TRouteElementPair entries from Route2MultiMap at H & V, the first as a function re...
Definition: TrackUnit.cpp:20148
TTrain::CumulativeDelayedRandMinsOneTrain
double CumulativeDelayedRandMinsOneTrain
the running total of all random delays including knock-on delays for a single train,...
Definition: TrainUnit.h:451
TTrainController::TrainAdded
bool TrainAdded
true when a train has been added by a split (occurs outside the normal train introduction process)
Definition: TrainUnit.h:812
TActionVectorEntry::LocationName
AnsiString LocationName
Definition: TrainUnit.h:121
TTrainFormattedInformation::OneCompleteFormattedTrainVector
TOneCompleteFormattedTrainVector OneCompleteFormattedTrainVector
Definition: TrainUnit.h:284
ShuttleFinishedRemainingHere
@ ShuttleFinishedRemainingHere
Definition: TrainUnit.h:42
FailUnexpectedBuffers
@ FailUnexpectedBuffers
Definition: TrainUnit.h:40
TTrackElement::TrainIDOnBridgeOrFailedPointOrigSpeedLimit01
int TrainIDOnBridgeOrFailedPointOrigSpeedLimit01
Definition: TrackUnit.h:155
TRailGraphics::TempBackground
Graphics::TBitmap * TempBackground
Definition: GraphicUnit.h:909
TTrackElement::StationEntryStopLinkPos4
int StationEntryStopLinkPos4
Used for track at platforms ( 1 & 2) and non-station named locations (1 - 4) to mark the train front ...
Definition: TrackUnit.h:153
TTrain::TimeTimeLocArrived
bool TimeTimeLocArrived
indicates whether has arrived (true) or not when ActionVectorEntryPtr->FormatType == TimeTimeLoc
Definition: TrainUnit.h:339
TActionType
TActionType
Used in LogAction when reporting a train action to the performance log & file.
Definition: TrainUnit.h:50
TTrainController::TContinuationAutoSigEntry::ThirdDelay
double ThirdDelay
Delays in seconds before consecutive signal changes - these correspond to the times taken for trains ...
Definition: TrainUnit.h:730
TTrain::LeadEntryPos
int LeadEntryPos
Definition: TrainUnit.h:373
TTrainController::LateArrivals
int LateArrivals
Definition: TrainUnit.h:849
TRailGraphics::CodeZ
Graphics::TBitmap * CodeZ
Definition: GraphicUnit.h:1025
NoShuttleLink
@ NoShuttleLink
Definition: TrainUnit.h:82
TTrainController::ReplotTrains
void ReplotTrains(int Caller, TDisplay *Disp)
plot all trains on the display
Definition: TrainUnit.cpp:10112
TTrainController::CheckShuttleServiceIntegrity
bool CheckShuttleServiceIntegrity(int Caller, TTrainDataEntry *TDEntryPtr, bool GiveMessages)
Check that each shuttle service ends either in Fns or Fxx-sh (though a single service can't end in Fx...
Definition: TrainUnit.cpp:16352
TTrain::FloatingTimetableString
AnsiString FloatingTimetableString(int Caller, TActionVectorEntry *Ptr)
Used in the floating window to display the timetable.
Definition: TrainUnit.cpp:7820
TAllRoutes::GetModifiableRouteAt
TOneRoute & GetModifiableRouteAt(int Caller, int At)
Returns a modifiable reference to the route at AllRoutesVector position 'At', after performing range ...
Definition: TrackUnit.cpp:19420
TTrain::NewShuttleFromNonRepeatService
void NewShuttleFromNonRepeatService(int Caller, bool NoLogFlag)
Carry out the actions needed when a new shuttle service is created from a non-repeating (F-nshs) serv...
Definition: TrainUnit.cpp:6677
TTrain::AValue
double AValue
< only true when a train has become a follow-on service early and the follow-on service normally pass...
Definition: TrainUnit.h:423
FailUnexpectedExitRailway
@ FailUnexpectedExitRailway
Definition: TrainUnit.h:40
TTrain::FrontTrainSplit
void FrontTrainSplit(int Caller)
Carry out the actions needed when a train is to split from the front.
Definition: TrainUnit.cpp:5805
TTrainController::WithinTimeRange
bool WithinTimeRange(int Caller, AnsiString Time1, AnsiString Time2, int MinuteRange)
check whether the two times are within the range in minutes specified and return true if so....
Definition: TrainUnit.cpp:20355
TTrainController::WriteTrainsToImage
void WriteTrainsToImage(int Caller, Graphics::TBitmap *Bitmap)
Called by TInterface::SaveOperatingImage1Click) to write all trains to the image file.
Definition: TrainUnit.cpp:10130
TActionVectorEntry::LinkedTrainEntryPtr
TTrainDataEntry * LinkedTrainEntryPtr
link pointer for use between fsp/rsp & Sfs; Fjo & jbo; Fns & Sns; & all shuttle to shuttle links
Definition: TrainUnit.h:145
TTrainController::AtLocSuccessor
bool AtLocSuccessor(const TActionVectorEntry &AVEntry)
A shorthand function that returns true if the successor to a given timetable action command should be...
Definition: TrainUnit.cpp:14910
TTrain::ZeroPowerNoJoinedByMessage
bool ZeroPowerNoJoinedByMessage
Definition: TrainUnit.h:348
TTrainController::SaveSessionContinuationAutoSigEntries
void SaveSessionContinuationAutoSigEntries(int Caller, std::ofstream &SessionFile)
save ContinuationAutoSigEntries to a session file
Definition: TrainUnit.cpp:17050
TTrainOperatingData::TrainID
int TrainID
Definition: TrainUnit.h:185
TUtilities::MaxRandomRepairTime
int MaxRandomRepairTime
Definition: Utilities.h:71
TDisplay::GetOutputLog3
TLabel * GetOutputLog3()
Definition: DisplayUnit.h:155
NoLocation
@ NoLocation
Definition: TrainUnit.h:72
TTrainController::CheckCrossReferencesAndSetData
bool CheckCrossReferencesAndSetData(int Caller, AnsiString SoughtHeadCode, AnsiString SeekingHeadCode, bool Shuttle, bool SetDataAndCheckLocations, bool GiveMessages)
A timetable validation function where all service cross references are checked for validity and set p...
Definition: TrainUnit.cpp:15031
TRailGraphics::bmTransparentBgnd
Graphics::TBitmap * bmTransparentBgnd
Definition: GraphicUnit.h:936
TTrainController::MinsToAnsiTime
AnsiString MinsToAnsiTime(int Input)
converts an integer minute value to string "HH:MM" added at v2.15.0
Definition: TrainUnit.cpp:16437
TTrain::FirstHalfMove
bool FirstHalfMove
true when the train is on the first half of an element when it displays as fully on two elements....
Definition: TrainUnit.h:395
FailLocTooShort
@ FailLocTooShort
Definition: TrainUnit.h:39
TTrainDataEntry::Mass
int Mass
in kg
Definition: TrainUnit.h:219
NoMode
@ NoMode
Definition: TrainUnit.h:60
TTrainController::LatePasses
int LatePasses
Definition: TrainUnit.h:851
TTrainController::SplitRepeat
bool SplitRepeat(int Caller, AnsiString OneEntry, int &RearStartOrRepeatMins, int &FrontStartOrRepeatDigits, int &RepeatNumber, bool GiveMessages)
Parse a timetable repeat entry, return true for success.
Definition: TrainUnit.cpp:12795
TFixedTrackPiece::TrackType
TTrackType TrackType
the type of track element
Definition: TrackUnit.h:100
TTrainController::TLocServiceTimes::ArrTime
AnsiString ArrTime
Definition: TrainUnit.h:779
TTrainController::SSHigh
bool SSHigh
Definition: TrainUnit.h:822
TimeLoc
@ TimeLoc
Definition: TrainUnit.h:66
TActionVectorIterator
TActionVector::iterator TActionVectorIterator
iterator
Definition: TrainUnit.h:176
TPrefDirElement
Basic preferred direction or route element - track element with additional members.
Definition: TrackUnit.h:201
TTrainController::AvHoursIntValue
int AvHoursIntValue
Input in MTBFEditBox in timetable hours, min value is 1 and max is 10,000. Here because performance f...
Definition: TrainUnit.h:872
TTrainController::CrashedTrains
int CrashedTrains
Definition: TrainUnit.h:843
TTrainController::TTEditPanelVisible
bool TTEditPanelVisible
new at v2.6.0 so potential error message only shows in TTEdit mode
Definition: TrainUnit.h:820
TDisplay::WarningLog
void WarningLog(int Caller, AnsiString Statement)
Display warning message Statement in the bottom left hand warning position and scroll other messages ...
Definition: DisplayUnit.cpp:523
TTrain::StoppedAtSignal
bool StoppedAtSignal
Definition: TrainUnit.h:493
TTrainController::GetControllerTrainTime
TDateTime GetControllerTrainTime(int Caller, TDateTime Time, int RepeatNumber, int IncrementalMinutes)
Get the interval between repeats.
Definition: TrainUnit.cpp:10514
TRailGraphics::Code_u
Graphics::TBitmap * Code_u
Definition: GraphicUnit.h:984
TTrackElement::Attribute
int Attribute
special variable used only for points, signals & level crossings, ignored otherwise; points 0=set to ...
Definition: TrackUnit.h:143
TStraddle
TStraddle
Defines the train position with respect to the track elements; three consecutive elements are Lead (f...
Definition: TrainUnit.h:299
FailEntryRouteSetAgainst
@ FailEntryRouteSetAgainst
Definition: TrainUnit.h:43
TTrain::TrainSkippedEvents
int TrainSkippedEvents
stores the pointer increment from the current action in ActionVector for skipped actions when a depar...
Definition: TrainUnit.h:383
TTrainController::THCandTrainPosParam
std::pair< AnsiString, int > THCandTrainPosParam
Definition: TrainUnit.h:791
TExitInfo::ServiceReference
AnsiString ServiceReference
Definition: TrainUnit.h:105
TTrainDataVector
std::vector< TTrainDataEntry > TTrainDataVector
vector class for containing the whole timetable - one entry per timetable service entry (the object i...
Definition: TrainUnit.h:242
TTrainController::GetRepeatTime
TDateTime GetRepeatTime(int Caller, TDateTime BasicTime, int RepeatNumber, int IncMinutes)
Return the repeating service time.
Definition: TrainUnit.cpp:16025
clCrashedBackground
#define clCrashedBackground
Definition: GraphicUnit.h:294
TTrack::IsLCAtHV
bool IsLCAtHV(int Caller, int HLoc, int VLoc)
True if a level crossing is found at H & V.
Definition: TrackUnit.cpp:7567
TTrain::OpTimeToAct
float OpTimeToAct
in minutes: new at v2.2.0 for operator time to act panel. Calculated in UpdateTrain,...
Definition: TrainUnit.h:459
TTrainController::TTClockTime
TDateTime TTClockTime
the time indicated by the timetable clock
Definition: TrainUnit.h:717
TAllRoutes::TLockedRouteClass::LockStartTime
TDateTime LockStartTime
the timetable time at which the route is locked, to start the 2 minute clock
Definition: TrackUnit.h:1665
TActionVectorEntry::SequenceType
TTimetableSequenceType SequenceType
indicates where in the sequence of codes the action lies
Definition: TrainUnit.h:141
TTrain::RearStartExitPos
int RearStartExitPos
the LinkPos value for the rear starting element (i.e. links to the front starting element)
Definition: TrainUnit.h:365
TTrainController::TLocServiceTimes::FrhMarker
AnsiString FrhMarker
Definition: TrainUnit.h:781
TRailGraphics::Code_o
Graphics::TBitmap * Code_o
Definition: GraphicUnit.h:978
TRailGraphics::CodeV
Graphics::TBitmap * CodeV
Definition: GraphicUnit.h:1021
TTrainController::TLocServiceTimes::ServiceAndRepeatNum
AnsiString ServiceAndRepeatNum
Definition: TrainUnit.h:777
TTrain::SetTrainElementID
void SetTrainElementID(int Caller, unsigned int TrackVectorPosition, int EntryPos)
When a train moves onto an element that element has its TrainIDOnElement value set to the TrainID val...
Definition: TrainUnit.cpp:3310
TActionVectorEntry::NonRepeatingShuttleLinkHeadCode
AnsiString NonRepeatingShuttleLinkHeadCode
Definition: TrainUnit.h:121
TActionVectorEntry::ShuttleLinkType
TTimetableShuttleLinkType ShuttleLinkType
indicates whether or not the action relates to a shuttle service link
Definition: TrainUnit.h:143
TTrain::TimetableFinished
bool TimetableFinished
set when there are no more timetable actions
Definition: TrainUnit.h:417
TUtilities::CallLog
std::deque< AnsiString > CallLog
call stack store, saved to the errorlog for diagnostic purposes
Definition: Utilities.h:105
TRailGraphics::CodeN
Graphics::TBitmap * CodeN
Definition: GraphicUnit.h:1013
TActionVectorEntry::Command
AnsiString Command
Definition: TrainUnit.h:121
TTrain::RepeatShuttleOrRemainHere
void RepeatShuttleOrRemainHere(int Caller, bool NoLogFlag)
Carry out the actions needed to create either a new shuttle service or (if all repeats have finished)...
Definition: TrainUnit.cpp:6731
TTrain::CallingOnAllowed
bool CallingOnAllowed(int Caller)
True if the train can be called on at its current position - see detail in .cpp file.
Definition: TrainUnit.cpp:5032
TTrainController::TContinuationTrainExpectationEntry::HeadCode
AnsiString HeadCode
< service description
Definition: TrainUnit.h:750
TTrackElement::Length23
int Length23
Definition: TrackUnit.h:151
TTrain::SignallerStoppingFlag
bool SignallerStoppingFlag
set when the signaller stop command has been given
Definition: TrainUnit.h:409
TTrainController::NotStartedTrainLateArr
int NotStartedTrainLateArr
total number of arrivals & departures for trains that haven't started yet for locations not reached y...
Definition: TrainUnit.h:868
TUtilities::LoadFileBool
bool LoadFileBool(std::ifstream &InFile)
loads a bool value from the file
Definition: Utilities.cpp:145
TTrainController::SigSLow
bool SigSLow
Message flags in TT checks to stop being given twice.
Definition: TrainUnit.h:822
TRailGraphics::Code_m
Graphics::TBitmap * Code_m
Definition: GraphicUnit.h:976
TTrack::AnyLinkedBarrierDownVectorManual
bool AnyLinkedBarrierDownVectorManual(int Caller, int HLoc, int VLoc, int &BDVectorPos)
Checks BarrierDownVector and returns true if there is one that is linked to the LC at H & V positions...
Definition: TrackUnit.cpp:6591
TRailGraphics::gl92set
Graphics::TBitmap * gl92set
Definition: GraphicUnit.h:728
SNSShuttle
@ SNSShuttle
Definition: TrainUnit.h:66
RemoveTrain
@ RemoveTrain
Definition: TrainUnit.h:52
TTrainController::TLocServiceTimes::DepTime
AnsiString DepTime
Definition: TrainUnit.h:780
TTrainController::SetWarningFlags
void SetWarningFlags(int Caller)
This sets all the warning flags (CrashWarning, DerailWarning etc) to their required states after a se...
Definition: TrainUnit.cpp:21520
TDisplay::PlotSmallOutput
void PlotSmallOutput(int Caller, int HPos, int VPos, Graphics::TBitmap *PlotItem)
Plot small (4x4) graphic PlotItem on the zoomed-out display at HPos & Vpos.
Definition: DisplayUnit.cpp:114
THVShortPair
std::pair< int, int > THVShortPair
Definition: InterfaceUnit.h:82
TTrainController::TContinuationTrainExpectationEntry
Class that stores data for trains expected at continuation entries (kept in a multimap - see below),...
Definition: TrainUnit.h:746
TTrainController::TContinuationTrainExpectationEntry::FixedDescription
AnsiString FixedDescription
Definition: TrainUnit.h:748
SNSNonRepeatFromShuttle
@ SNSNonRepeatFromShuttle
Definition: TrainUnit.h:66
TTrainController::TotEarlyPassMins
float TotEarlyPassMins
Definition: TrainUnit.h:835
TRailGraphics::CodeH
Graphics::TBitmap * CodeH
Definition: GraphicUnit.h:1007
TTrainController::OpTimeToActMultiMap
TOpTimeToActMultiMap OpTimeToActMultiMap
added v2.2.0 for Op time to act display
Definition: TrainUnit.h:886
TTrainController::TContinuationAutoSigEntry::AccessNumber
int AccessNumber
the number of times the signal changing function has been accessed - starts at 0 and increments after...
Definition: TrainUnit.h:732
TActionVectorEntry::Reminder
unsigned int Reminder
Definition: TrainUnit.h:149
TActionVectorEntry::SignallerControl
bool SignallerControl
< string values for timetabled event entries, null on creation
Definition: TrainUnit.h:125
TTrain::GetTrainTime
TDateTime GetTrainTime(int Caller, TDateTime Time)
Returns the timetable action time corresponding to 'Time' for this train, i.e. it adjusts the time va...
Definition: TrainUnit.cpp:5312
TTrain::BrakeRate
double BrakeRate
the current train brake rate
Definition: TrainUnit.h:441
TUtilities::LastDelayTTClockTime
double LastDelayTTClockTime
Clock time at which the latest delay for any train occurred. Used to prevent new delays within 5 minu...
Definition: Utilities.h:85
TTrackElement::SpeedLimit23
int SpeedLimit23
Element lengths and speed limits, ...01 is for the track with link positions [0] and [1],...
Definition: TrackUnit.h:151
TTrainController::TContinuationAutoSigEntry::PassoutTime
TDateTime PassoutTime
the timetable clock time at which the train exits from the continuation
Definition: TrainUnit.h:736
TTrain::FinishJoinLogSent
bool FinishJoinLogSent
Definition: TrainUnit.h:343
TTrack::GapFlashFlag
bool GapFlashFlag
true when a pair of connected gaps is flashing
Definition: TrackUnit.h:756
TRailGraphics::gl95set
Graphics::TBitmap * gl95set
Definition: GraphicUnit.h:732
TrainUnit.h
PassTime
@ PassTime
Definition: TrainUnit.h:67
TTrainController::IncorrectExits
int IncorrectExits
Definition: TrainUnit.h:848
TTrain::ContinuationExit
bool ContinuationExit(int Caller, int Element, int Exitpos) const
True if Element is a continuation and Exitpos is the continuation end.
Definition: TrainUnit.cpp:3234
TFixedTrackPiece::Link
int Link[4]
Track connection link values, max. of 4, unused = -1, top lh diag = 1, top = 2, top rh diag = 3,...
Definition: TrackUnit.h:90
TTrain::UpdateTrain
void UpdateTrain(int Caller)
Major function called at each clock tick for each train & handles all train movement & associated act...
Definition: TrainUnit.cpp:654
TRailGraphics::LockedRouteCancelPtr
Graphics::TBitmap * LockedRouteCancelPtr[10]
for locked route cancel graphic, 1 for each of 8 links, 0 & 5 included as for direction
Definition: GraphicUnit.h:1074
TRailGraphics::Code8
Graphics::TBitmap * Code8
Definition: GraphicUnit.h:998
TTrainController::SameDirection
bool SameDirection(int Caller, AnsiString Ref1, AnsiString Ref2, AnsiString Time1, AnsiString Time2, int RepeatNum1, int RepeatNum2, TServiceCallingLocsList List1, TServiceCallingLocsList List2, AnsiString Location, bool Arrival)
Determines whether two services are running in the same direction when they arrive or depart from Loc...
Definition: TrainUnit.cpp:20639
TTrain::TrainToBeJoinedByIsAdjacent
bool TrainToBeJoinedByIsAdjacent(int Caller, TTrain *&TrainToBeJoinedBy)
True for a train waiting to be joined when the joining train is adjacent.
Definition: TrainUnit.cpp:6648
TTrackElement::TrainIDOnBridgeOrFailedPointOrigSpeedLimit23
int TrainIDOnBridgeOrFailedPointOrigSpeedLimit23
Definition: TrackUnit.h:155
TTrain::SetTrainMovementValues
void SetTrainMovementValues(int Caller, int TrackVectorPosition, int EntryPos)
Calculates train speeds and times for the element that the train is about to enter....
Definition: TrainUnit.cpp:3730
TActionVectorEntry::Warning
bool Warning
if set triggers an alert in the warning and perf log panels when the action is reached
Definition: TrainUnit.h:127
TTrain::PickUpBackgroundBitmap
void PickUpBackgroundBitmap(int Caller, int HOffset, int VOffset, int Element, int EntryPos, Graphics::TBitmap *GraphicPtr) const
Store the background bitmap pointer (BackgroundPtr - see above) prior to being overwritten by the tra...
Definition: TrainUnit.cpp:2822
clStoppedTrainInFront
#define clStoppedTrainInFront
Definition: GraphicUnit.h:303
TDisplay::GetOutputLog10
TLabel * GetOutputLog10()
Definition: DisplayUnit.h:190
TTrainDataEntry::ServiceReference
AnsiString ServiceReference
Definition: TrainUnit.h:209
TTrain::SignallerChangeTrainDirection
void SignallerChangeTrainDirection(int Caller)
Unplots & replots train, which checks for facing signal and sets StoppedAtSignal if req'd.
Definition: TrainUnit.cpp:7011
TTrain::ZeroPowerNoRearSplitMessage
bool ZeroPowerNoRearSplitMessage
Definition: TrainUnit.h:346
FailMissedChangeDirection
@ FailMissedChangeDirection
Definition: TrainUnit.h:41
NotSet
@ NotSet
Definition: TrackUnit.h:76
Repeat
@ Repeat
Definition: TrainUnit.h:67
TRailGraphics::Code_c
Graphics::TBitmap * Code_c
Definition: GraphicUnit.h:966
TTrainController::StripExcessFromHeadCode
void StripExcessFromHeadCode(int Caller, AnsiString &HeadCode)
change an extended headcode to an ordinary 4 character headcode
Definition: TrainUnit.cpp:14919
FinishSequence
@ FinishSequence
Definition: TrainUnit.h:77
TTrack::TIMPair
std::pair< unsigned int, unsigned int > TIMPair
TrackElement pair type used for inactive elements, values are vector positions.
Definition: TrackUnit.h:678
TUtilities::Clock2Stopped
bool Clock2Stopped
when true the main loop - Interface->ClockTimer2 - is stopped
Definition: Utilities.h:75
TTrainController::MRSHigh
bool MRSHigh
Definition: TrainUnit.h:822
TTrack::OtherTrainOnTrack
bool OtherTrainOnTrack(int Caller, int TrackPos, int LinkPos, int OwnTrainID)
True if another train on LinkPos track of element at TrackPos, whether bridge or not,...
Definition: TrackUnit.cpp:11673
TTrackElement::GroundSignal
@ GroundSignal
Definition: TrackUnit.h:161
LevelCrossing
@ LevelCrossing
Definition: TrackUnit.h:67
TDisplay::GetOutputLog2
TLabel * GetOutputLog2()
Definition: DisplayUnit.h:150
TTrain::NotInService
bool NotInService
Definition: TrainUnit.h:494
TRailGraphics::CodeA
Graphics::TBitmap * CodeA
Definition: GraphicUnit.h:1000
TTrack::ActiveTrackElementNameMapCompiledFlag
bool ActiveTrackElementNameMapCompiledFlag
indicates that the ActiveTrackElementNameMap has been compiled
Definition: TrackUnit.h:750
TTrainController::LocServiceTimesLocationSort
bool LocServiceTimesLocationSort(TLocServiceTimes i, TLocServiceTimes j)
Definition: TrainUnit.h:897
TOneTrainFormattedEntry
A single train timetable action for use in a formatted timetable.
Definition: TrainUnit.h:250
TTrain::RearTrainSplit
void RearTrainSplit(int Caller)
Carry out the actions needed when a train is to split from the rear.
Definition: TrainUnit.cpp:5965
TAllRoutes::TLockedRouteClass::RearTrackVectorPosition
unsigned int RearTrackVectorPosition
the TrackVector position of the rearmost element selected for truncation (this will be truncated)
Definition: TrackUnit.h:1659
TTrainController::ConsolidateSARNTArrDep
AnsiString ConsolidateSARNTArrDep(int Caller, const AnsiString Input, int &NumTrainsAtLoc, AnsiString Location, bool Arrival, bool &AnalysisError, int &MaxNumberOfSameDirections)
Removes duplicates from and sorts ServiceAndRepeatNumTotal into alphabetical order for arrivals (bool...
Definition: TrainUnit.cpp:20381
Track
TTrack * Track
the object pointer, object created in InterfaceUnit
Definition: TrackUnit.cpp:54
TTrackElement::Conn
int Conn[4]
Connecting element position in TrackVector, set to -1 if no connecting link or if track not linked.
Definition: TrackUnit.h:145
Signaller
@ Signaller
Definition: TrainUnit.h:60
TTrain::TrainCrashedInto
int TrainCrashedInto
the TrainID of the train that this train has crashed into, recorded so that train can be marked and d...
Definition: TrainUnit.h:506
TRailGraphics::smYellow
Graphics::TBitmap * smYellow
Definition: GraphicUnit.h:907
RailGraphics
TRailGraphics * RailGraphics
the object pointer, object created in InterfaceUnit
Definition: GraphicUnit.cpp:50
TTrainOperatingData
Data for a specific train for use during operation.
Definition: TrainUnit.h:183
FailCreatePoints
@ FailCreatePoints
Definition: TrainUnit.h:39
TRailGraphics::smBlack
Graphics::TBitmap * smBlack
Definition: GraphicUnit.h:895
TTrain::TreatPassAsTimeLocDeparture
bool TreatPassAsTimeLocDeparture
< indicates failure
Definition: TrainUnit.h:421
Bridge
@ Bridge
Definition: TrackUnit.h:66
TRailGraphics::Code_p
Graphics::TBitmap * Code_p
Definition: GraphicUnit.h:979
TTrainFormattedInformation::NumberOfTrains
int NumberOfTrains
number of repeats + 1
Definition: TrainUnit.h:282
TTrain::LagExitPos
int LagExitPos
TrackVector positions, & entry & exit connection positions for the elements that the train occupies.
Definition: TrainUnit.h:373
TTimetableSequenceType
TTimetableSequenceType
Definition: TrainUnit.h:76
TTrain::TimetableMaxRunningSpeed
double TimetableMaxRunningSpeed
the maximum train running speed when in timetable mode (see int SignallerMaxSpeed for signaller contr...
Definition: TrainUnit.h:433
SequTypeForRepeatEntry
@ SequTypeForRepeatEntry
Definition: TrainUnit.h:77
TRailGraphics::bmName
Graphics::TBitmap * bmName
Definition: GraphicUnit.h:528
clB0G0R0
#define clB0G0R0
Definition: GraphicUnit.h:37
Buffers
@ Buffers
Definition: TrackUnit.h:66
TActionVectorEntry::RearStartOrRepeatMins
int RearStartOrRepeatMins
Definition: TrainUnit.h:131
clFrontCodeTimetable
#define clFrontCodeTimetable
Definition: GraphicUnit.h:297
TTrack::OneNonStationLongEnoughForSplit
bool OneNonStationLongEnoughForSplit(int Caller, AnsiString LocationName)
As below but here allow points & crossovers.
Definition: TrackUnit.cpp:11097
TActionVectorEntry::OtherHeadCode
AnsiString OtherHeadCode
Definition: TrainUnit.h:121